Files
djk/internal/app/provider_hongguo_search.go

108 lines
3.2 KiB
Go

package app
import (
"context"
"errors"
"net/url"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
)
type hongguoSearchEntry struct {
Dramas []Drama
Total int
ExpiresAt time.Time
}
type hongguoSearchCall struct {
done chan struct{}
entry hongguoSearchEntry
err error
}
func hongguoSearchKeyword(keyword string) (string, error) {
keyword = strings.TrimSpace(keyword)
if keyword == "" || !utf8.ValidString(keyword) || utf8.RuneCountInString(keyword) > 80 || strings.IndexFunc(keyword, unicode.IsControl) >= 0 {
return "", errors.New("请输入 1 至 80 个字符的搜索词")
}
return keyword, nil
}
func (downloader *Downloader) searchHongguoDramas(ctx context.Context, keyword string) (hongguoSearchEntry, error) {
keyword, err := hongguoSearchKeyword(keyword)
if err != nil {
return hongguoSearchEntry{}, err
}
client := downloader.hongguoClient()
client.mu.Lock()
if cached, found := client.searches[keyword]; found && time.Now().Before(cached.ExpiresAt) {
client.mu.Unlock()
return cached, nil
}
if pending := client.searchPending[keyword]; pending != nil {
client.mu.Unlock()
select {
case <-ctx.Done():
return hongguoSearchEntry{}, ctx.Err()
case <-pending.done:
if (errors.Is(pending.err, context.Canceled) || errors.Is(pending.err, context.DeadlineExceeded)) && ctx.Err() == nil {
return downloader.searchHongguoDramas(ctx, keyword)
}
return pending.entry, pending.err
}
}
pending := &hongguoSearchCall{done: make(chan struct{})}
client.searchPending[keyword] = pending
client.mu.Unlock()
entry, err := downloader.fetchHongguoSearch(ctx, keyword)
client.mu.Lock()
if err == nil {
if len(client.searches) >= 64 {
client.searches = map[string]hongguoSearchEntry{}
}
entry.ExpiresAt = time.Now().Add(5 * time.Minute)
client.searches[keyword] = entry
}
pending.entry, pending.err = entry, err
delete(client.searchPending, keyword)
close(pending.done)
client.mu.Unlock()
return entry, err
}
func (downloader *Downloader) fetchHongguoSearch(ctx context.Context, keyword string) (hongguoSearchEntry, error) {
pageURL := hongguoBaseURL + "/search/" + url.PathEscape(keyword)
body, err := downloader.fetchProviderText(ctx, pageURL, hongguoBaseURL+"/")
if err != nil {
return hongguoSearchEntry{}, err
}
page := routerLoaderMap(parseRouterData(body), "search_(keyword)/page", "search_")
rows, valid := page["searchList"].([]any)
if page["isSuccess"] != true || !valid || mapString(page, "query") != keyword {
return hongguoSearchEntry{}, errors.New("红果搜索未返回有效结果")
}
entry := hongguoSearchEntry{Dramas: make([]Drama, 0, len(rows))}
seen := make(map[string]bool, len(rows))
for _, row := range rows {
if len(nestedMap(row, "video_data")) == 0 {
continue
}
drama := hongguoDramaFromAny(row, "短剧")
if drama.ID != "" && !seen[drama.ID] {
seen[drama.ID] = true
entry.Dramas = append(entry.Dramas, drama)
}
}
if len(rows) > 0 && len(entry.Dramas) == 0 {
return hongguoSearchEntry{}, errors.New("红果搜索结果中没有可识别的剧集")
}
entry.Total, _ = strconv.Atoi(mapString(page, "totalCount"))
if entry.Total < len(entry.Dramas) {
entry.Total = len(entry.Dramas)
}
return entry, nil
}