108 lines
3.2 KiB
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
|
|
}
|