Files
djk/internal/app/provider_hongguo_recommendations.go

111 lines
3.7 KiB
Go

package app
import (
"context"
"errors"
"net/http"
"strconv"
"strings"
)
type hongguoRecommendationQuery struct {
Genre string `json:"genre"`
Offset int `json:"offset"`
SessionID string `json:"sessionId"`
Seen []string `json:"seen"`
}
type hongguoRecommendationPage struct {
Dramas []Drama `json:"data"`
NextOffset int `json:"nextOffset"`
SessionID string `json:"sessionId"`
HasMore bool `json:"hasMore"`
Saved bool `json:"saved"`
}
func (query hongguoRecommendationQuery) validate() error {
known := false
for _, genre := range hongguoAppGenres {
known = known || query.Genre == genre.key
}
if !known || query.Offset < 0 || query.Offset > 1_000_000 || len(query.SessionID) > 4096 || strings.ContainsAny(query.SessionID, "\r\n\x00") || len(query.Seen) > 540 {
return errors.New("推荐分类或分页参数无效,请重新获取")
}
for _, id := range query.Seen {
if !hongguoNumericID.MatchString(strings.TrimPrefix(id, "hongguo:")) {
return errors.New("推荐分页含无效剧集 ID")
}
}
return nil
}
func (downloader *Downloader) fetchHongguoRecommendations(ctx context.Context, query hongguoRecommendationQuery) (hongguoRecommendationPage, error) {
page := hongguoRecommendationPage{Dramas: []Drama{}}
if err := query.validate(); err != nil {
return page, err
}
scene, category := "", ""
for _, genre := range hongguoAppGenres {
if genre.key == query.Genre {
scene, category = genre.scene, genre.name
}
}
seen := make(map[string]bool, len(query.Seen))
filterIDs := make([]string, 0, len(query.Seen))
for _, id := range query.Seen {
id = strings.TrimPrefix(id, "hongguo:")
if !seen[id] {
filterIDs = append(filterIDs, id)
seen[id] = true
}
}
payload := map[string]any{
"req_scene": scene, "offset": query.Offset, "limit": 18,
"req_type": "only_content", "need_selector_panel": false, "client_req_type": 3,
"session_id": query.SessionID, "filter_ids": strings.Join(filterIDs, ","),
"select_items": map[string]any{
"genre": []string{query.Genre}, "sort": []string{}, "gender": []string{},
"category_dim_theme": []string{}, "category_dim_role": []string{}, "category_dim_epoch": []string{},
"online_time": []string{}, "creation_status": []string{},
},
}
if query.Offset > 0 {
payload["client_req_type"] = 2
}
result, err := downloader.hongguoAppRequest(ctx, http.MethodPost, "/reading/distribution/category/landpage/v/", nil, payload)
if err != nil {
return page, err
}
data := nestedMap(result, "data")
rows, valid := data["video_data"].([]any)
hasMore, paginationOK := data["has_more"].(bool)
next, parseErr := strconv.Atoi(mapString(data, "next_offset"))
if !valid || !paginationOK || hasMore && parseErr != nil {
return page, errors.New("红果推荐数据格式异常,已保留上次位置")
}
validRows := 0
for _, row := range rows {
drama := hongguoDramaFromAny(row, category)
if drama.ID != "" {
validRows++
}
id := strings.TrimPrefix(drama.ID, "hongguo:")
if drama.ID == "" || seen[id] {
continue
}
seen[id] = true
page.Dramas = append(page.Dramas, drama)
}
if len(rows) > 0 && validRows == 0 {
return hongguoRecommendationPage{}, errors.New("红果推荐未返回可识别的剧集")
}
if hasMore && (next <= query.Offset || next > 1_000_000 || len(page.Dramas) == 0) {
return hongguoRecommendationPage{}, errors.New("红果推荐分页未前进,可重试或重新获取")
}
page.NextOffset, page.HasMore, page.SessionID = next, hasMore, mapString(data, "session_id")
if len(page.SessionID) > 4096 || strings.ContainsAny(page.SessionID, "\r\n\x00") {
return hongguoRecommendationPage{}, errors.New("红果推荐分页标记无效")
}
return page, ctx.Err()
}