package app import ( "context" "encoding/json" "errors" "fmt" "net/http" "sort" "strconv" "strings" "time" ) func (downloader *Downloader) resolveHongguoAppMedia(ctx context.Context, videoID string) (providerMedia, error) { if !hongguoNumericID.MatchString(videoID) { return providerMedia{}, errors.New("红果视频 ID 无效") } payload := map[string]any{ "video_id": videoID, "content_type": 1, "biz_param": map[string]any{"need_all_video_definition": true, "video_platform": 3}, } result, err := downloader.hongguoAppRequest(ctx, http.MethodPost, "/novel/player/video_model/v1/", nil, payload) if err != nil { return providerMedia{}, err } data := nestedMap(result, "data") model, _ := data["video_model"].(map[string]any) if encoded, ok := data["video_model"].(string); ok { decoder := json.NewDecoder(strings.NewReader(encoded)) decoder.UseNumber() if err := decoder.Decode(&model); err != nil { return providerMedia{}, errors.New("红果 App 播放信息格式异常") } } return selectHongguoAppMedia(model) } func selectHongguoAppMedia(model map[string]any) (providerMedia, error) { variants := anyList(model["video_list"]) if rows, ok := model["video_list"].(map[string]any); ok && len(variants) == 0 { keys := make([]string, 0, len(rows)) for key := range rows { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { variants = append(variants, rows[key]) } } duration, _ := strconv.ParseFloat(mapString(model, "video_duration", "duration"), 64) var selected providerMedia bestQuality := -1 var keyErr error choices := make(map[int]providerMedia) scores := make(map[int]int) for _, row := range variants { variant, _ := row.(map[string]any) meta := nestedMap(variant, "video_meta") codec := strings.ToLower(mapString(meta, "codec_type")) if codec == "bytevc2" || strings.Contains(strings.ToLower(mapString(variant, "gear_des_key")), "bytevc2") { continue } address := mapString(variant, "main_url") if len(address) > 8192 || !isProviderHTTPMediaURL(address) { continue } media := providerMedia{URL: address, Referer: "https://novel.snssdk.com/", Duration: time.Duration(duration * float64(time.Second))} encryption := nestedMap(variant, "encrypt_info") spade := mapString(encryption, "spade_a") if spade != "" || encryption["encrypt"] == true || mapString(encryption, "encryption_method") == "cenc-aes-ctr" { var err error media.CENCKey, err = hongguoContentKey(spade) if err != nil { keyErr = err continue } } height, _ := strconv.Atoi(mapString(meta, "vheight")) if definition, err := strconv.Atoi(hongguoQualityNumber.FindString(mapString(meta, "definition"))); err == nil && definition > 0 { height = definition } else if width, _ := strconv.Atoi(mapString(meta, "vwidth")); width > 0 && (height == 0 || width < height) { height = width } media.Quality = height quality := height * 10 if codec == "h264" || codec == "avc1" { quality++ } if previous, exists := scores[height]; !exists || quality > previous { choices[height], scores[height] = media, quality } if selected.URL == "" || quality > bestQuality { selected, bestQuality = media, quality } } if selected.URL != "" { for _, media := range choices { selected.Variants = append(selected.Variants, media) } sort.Slice(selected.Variants, func(i, j int) bool { return selected.Variants[i].Quality > selected.Variants[j].Quality }) return selected, nil } if keyErr != nil { return providerMedia{}, fmt.Errorf("红果 App 媒体密钥不可用: %w", keyErr) } return providerMedia{}, errors.New("红果 App 未返回兼容的媒体,已跳过不支持的编码") }