111 lines
3.7 KiB
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()
|
|
}
|