Files
djk/internal/app/provider_media.go
T

169 lines
5.1 KiB
Go

package app
import (
"context"
"fmt"
"net/url"
"strconv"
"strings"
"time"
)
type providerMedia struct {
URL string
Referer string
Duration time.Duration
Playlist string
HLSKey []byte
CENCKey []byte
Quality int
Variants []providerMedia
}
func (d *Downloader) providerBaseURL(source string) string {
source = canonicalProviderSource(source)
var configured, fallback string
switch source {
case sourceHuangguoAI:
configured, fallback = d.cfg.HuangguoAIURL, huangguoAIBaseURL
case sourceHuangguoVideo:
configured, fallback = d.cfg.HuangguoVideoURL, huangguoVideoBaseURL
case sourceHuangdou:
configured, fallback = d.cfg.HuangdouURL, huangdouBaseURL
case sourceHongguo:
configured, fallback = d.cfg.HongguoURL, hongguoBaseURL
default:
fallback = "https://d2pypzndaqisk.cloudfront.net"
}
return strings.TrimRight(firstNonEmpty(configured, fallback), "/")
}
func providerSourceForURL(raw string) string {
parsed, err := url.Parse(raw)
if err != nil {
return ""
}
host := strings.ToLower(parsed.Hostname())
switch {
case host == "huangguoai.com" || strings.HasSuffix(host, ".ediayikma.cc") || strings.HasSuffix(host, ".agdkczeyx.cc"):
return sourceHuangguoAI
case host == "huangguo.video":
return sourceHuangguoVideo
case host == "tideember.cc" || host == "xqjurgek.top":
return sourceHuangdou
case host == "hongguoduanju.com" || host == "www.hongguoduanju.com":
return sourceHongguo
default:
return ""
}
}
func (d *Downloader) providerURLCandidates(raw string) []string {
source := providerSourceForURL(raw)
if source == "" {
return []string{raw}
}
parsed, _ := url.Parse(raw)
d.providerMu.Lock()
preferred := d.providerHosts[source]
d.providerMu.Unlock()
var candidates []string
seen := map[string]bool{}
add := func(candidate string) {
if candidate != "" && !seen[candidate] {
seen[candidate] = true
candidates = append(candidates, candidate)
}
}
add(rehostProviderURL(parsed, preferred))
configured := d.providerBaseURL(source)
add(rehostProviderURL(parsed, configured))
if providerSourceForURL(configured) == "" {
return candidates
}
for _, candidate := range providerURLCandidates(raw) {
add(candidate)
}
return candidates
}
func (d *Downloader) resolveProviderMedia(ctx context.Context, task Task) (providerMedia, error) {
chapter := task.Chapter
chapter.Source = canonicalProviderSource(chapter.Source)
if chapter.Source == "" {
chapter.Source = sourceFromDramaID(task.DramaID)
}
if strings.HasPrefix(chapter.VideoURL, "hongguo-cenc://") {
return d.resolveHongguoMedia(ctx, task)
}
if chapter.PageURL == "" && (chapter.Source == sourceHuangguoAI || chapter.Source == sourceHuangguoVideo) {
if source, sourceID, valid := splitProviderDramaID(task.DramaID); valid {
_, chapters, err := d.GetHuangguoChapters(ctx, source, sourceID)
if err != nil {
return providerMedia{}, fmt.Errorf("刷新旧任务播放地址失败: %w", err)
}
matched := false
for _, fresh := range chapters {
if fresh.ID == chapter.ID {
chapter = fresh
matched = true
break
}
}
if !matched {
return providerMedia{}, fmt.Errorf("原章节已变化,请更新该合集后重新下载")
}
}
}
media := providerMedia{URL: chapter.VideoURL, Referer: firstNonEmpty(chapter.Referer, d.providerBaseURL(chapter.Source)+"/")}
if chapter.Source == sourceHuangdou {
_, sourceID, valid := splitProviderDramaID(task.DramaID)
if valid {
sequence, err := strconv.Atoi(chapter.EpisodeString(task.Index))
if err != nil || sequence < 1 {
return providerMedia{}, fmt.Errorf("黄豆集数无效")
}
client := newHuangdouAPIClient(d)
media, err = d.resolveHuangdouPlayback(ctx, client, sourceID, sequence)
if err != nil {
return providerMedia{}, err
}
media.Referer = client.host + "/home"
}
}
if chapter.PageURL != "" && (chapter.Source == sourceHuangguoAI || chapter.Source == sourceHuangguoVideo) {
body, err := d.fetchProviderText(ctx, chapter.PageURL, media.Referer)
if err != nil {
return providerMedia{}, err
}
if chapter.Source == sourceHuangguoAI {
media.URL = parseAIVideoURL(body, chapter.PageURL)
} else {
media.URL = parseDataHLS(body, chapter.PageURL)
}
d.providerMu.Lock()
if preferred := d.providerHosts[chapter.Source]; preferred != "" {
media.Referer = preferred + "/"
}
d.providerMu.Unlock()
}
if !isProviderHTTPMediaURL(media.URL) {
return providerMedia{}, fmt.Errorf("%s 未返回有效播放地址,请刷新章节或确认站点访问权限", chapter.Source)
}
parsed, _ := url.Parse(media.URL)
if strings.HasSuffix(strings.ToLower(parsed.Path), ".m3u8") {
playlist, err := d.fetchProviderText(ctx, media.URL, media.Referer)
if err != nil {
return providerMedia{}, fmt.Errorf("获取播放列表失败: %w", err)
}
if !strings.HasPrefix(strings.TrimSpace(strings.TrimPrefix(playlist, "\ufeff")), "#EXTM3U") {
return providerMedia{}, fmt.Errorf("站点未返回有效 M3U8,可能需要登录或链接已失效")
}
if duration := m3u8Duration(playlist); duration > 0 {
media.Duration = duration
}
media.Playlist = playlist
}
return media, nil
}