初始提交:短剧库源码(含转码优化 ultrafast)
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user