refactor(playback): simplify handlers, http utils, and ranking
This commit is contained in:
@@ -203,6 +203,15 @@ func (c *allAnimeClient) GetEpisodes(ctx context.Context, showID string, mode st
|
||||
return episodes, nil
|
||||
}
|
||||
|
||||
func buildStreamSource(url, sourceType, provider string) StreamSource {
|
||||
return StreamSource{
|
||||
URL: url,
|
||||
Provider: provider,
|
||||
Type: sourceType,
|
||||
Referer: allAnimeReferer,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *allAnimeClient) GetEpisodeSources(ctx context.Context, showID string, episode string, mode string) ([]StreamSource, error) {
|
||||
graphqlQuery := `query($showId: String!, $translationType: VaildTranslationTypeEnumType!, $episodeString: String!) {
|
||||
episode(showId: $showId, translationType: $translationType, episodeString: $episodeString) {
|
||||
@@ -254,12 +263,7 @@ func (c *allAnimeClient) GetEpisodeSources(ctx context.Context, showID string, e
|
||||
sourceType = detectEmbedType(target)
|
||||
}
|
||||
|
||||
out = append(out, StreamSource{
|
||||
URL: target,
|
||||
Provider: ref.Name,
|
||||
Type: sourceType,
|
||||
Referer: allAnimeReferer,
|
||||
})
|
||||
out = append(out, buildStreamSource(target, sourceType, ref.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -274,12 +278,7 @@ func (c *allAnimeClient) GetEpisodeSources(ctx context.Context, showID string, e
|
||||
sourceType = detectEmbedType(decoded)
|
||||
}
|
||||
|
||||
out = append(out, StreamSource{
|
||||
URL: decoded,
|
||||
Provider: ref.Name,
|
||||
Type: sourceType,
|
||||
Referer: allAnimeReferer,
|
||||
})
|
||||
out = append(out, buildStreamSource(decoded, sourceType, ref.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -217,18 +217,6 @@ func (h *Handler) HandleProxy(w http.ResponseWriter, r *http.Request) {
|
||||
h.proxyUpstream(w, r, targetURL, referer)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleProxyStream(w http.ResponseWriter, r *http.Request) {
|
||||
h.HandleProxy(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleProxySegment(w http.ResponseWriter, r *http.Request) {
|
||||
h.HandleProxy(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleProxySubtitle(w http.ResponseWriter, r *http.Request) {
|
||||
h.HandleProxy(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleSaveProgress(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
|
||||
25
internal/features/playback/http_utils.go
Normal file
25
internal/features/playback/http_utils.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package playback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func doProxiedRequest(ctx context.Context, client *http.Client, url string, referer string) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
if referer != "" {
|
||||
req.Header.Set("Referer", referer)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -17,7 +17,7 @@ func (s *Service) SaveProgress(ctx context.Context, userID string, animeID int64
|
||||
return errors.New("invalid save progress input")
|
||||
}
|
||||
|
||||
txQueries, tx, err := s.beginTx(ctx)
|
||||
txQueries, tx, err := database.BeginTx(ctx, s.sqlDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func (s *Service) CompleteAnime(ctx context.Context, userID string, animeID int6
|
||||
return errors.New("invalid complete anime input")
|
||||
}
|
||||
|
||||
txQueries, tx, err := s.beginTx(ctx)
|
||||
txQueries, tx, err := database.BeginTx(ctx, s.sqlDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,16 +142,3 @@ func (s *Service) CompleteAnime(ctx context.Context, userID string, animeID int6
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) beginTx(ctx context.Context) (*database.Queries, *sql.Tx, error) {
|
||||
if s.sqlDB == nil {
|
||||
return nil, nil, errors.New("database unavailable")
|
||||
}
|
||||
|
||||
tx, err := s.sqlDB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
|
||||
return database.New(tx), tx, nil
|
||||
}
|
||||
|
||||
@@ -27,15 +27,7 @@ func newProviderExtractor() *providerExtractor {
|
||||
|
||||
func (e *providerExtractor) ExtractVideoLinks(ctx context.Context, providerPath string) ([]StreamSource, error) {
|
||||
endpoint := e.baseURL + providerPath
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create provider request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Referer", e.referer)
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
|
||||
resp, err := e.httpClient.Do(req)
|
||||
resp, err := doProxiedRequest(ctx, e.httpClient, endpoint, e.referer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetch provider response: %w", err)
|
||||
}
|
||||
@@ -133,17 +125,7 @@ func (e *providerExtractor) parseProviderResponse(ctx context.Context, response
|
||||
}
|
||||
|
||||
func (e *providerExtractor) parseM3U8(ctx context.Context, masterURL string, referer string) ([]StreamSource, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, masterURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if referer != "" {
|
||||
req.Header.Set("Referer", referer)
|
||||
}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
|
||||
resp, err := e.httpClient.Do(req)
|
||||
resp, err := doProxiedRequest(ctx, e.httpClient, masterURL, referer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -162,17 +162,17 @@ func (s *Service) issueProxyToken(targetURL string, referer string, scope proxyS
|
||||
})
|
||||
}
|
||||
|
||||
var proxyTokenTTLs = map[proxyScope]time.Duration{
|
||||
proxyScopeStream: proxyStreamTokenTTL,
|
||||
proxyScopeSegment: proxySegmentTokenTTL,
|
||||
proxyScopeSubtitle: proxySubtitleTokenTTL,
|
||||
}
|
||||
|
||||
func proxyTokenTTL(scope proxyScope) time.Duration {
|
||||
switch scope {
|
||||
case proxyScopeStream:
|
||||
return proxyStreamTokenTTL
|
||||
case proxyScopeSegment:
|
||||
return proxySegmentTokenTTL
|
||||
case proxyScopeSubtitle:
|
||||
return proxySubtitleTokenTTL
|
||||
default:
|
||||
return proxyStreamTokenTTL
|
||||
if ttl, ok := proxyTokenTTLs[scope]; ok {
|
||||
return ttl
|
||||
}
|
||||
return proxyStreamTokenTTL
|
||||
}
|
||||
|
||||
func (s *Service) resolveProxyToken(ctx context.Context, token string, scope proxyScope) (string, string, error) {
|
||||
|
||||
@@ -182,7 +182,7 @@ func (s *Service) BuildWatchPageData(ctx context.Context, malID int, titleCandid
|
||||
InitialMode: initialMode,
|
||||
AvailableModes: cloneSlice(baseData.AvailableModes),
|
||||
ModeSources: clientModeSources,
|
||||
Segments: cloneSegments(baseData.Segments),
|
||||
Segments: cloneSlice(baseData.Segments),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -360,16 +360,10 @@ func cloneModeSources(modeSources map[string]ModeSource) map[string]ModeSource {
|
||||
cloned[mode] = ModeSource{
|
||||
URL: source.URL,
|
||||
Referer: source.Referer,
|
||||
Subtitles: cloneSubtitleItems(source.Subtitles),
|
||||
Subtitles: cloneSlice(source.Subtitles),
|
||||
}
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func cloneSubtitleItems(items []SubtitleItem) []SubtitleItem {
|
||||
return cloneSlice(items)
|
||||
}
|
||||
|
||||
func cloneSegments(segments []SkipSegment) []SkipSegment {
|
||||
return cloneSlice(segments)
|
||||
}
|
||||
|
||||
@@ -17,13 +17,7 @@ func (s *Service) fetchSkipSegments(ctx context.Context, malID int, episode stri
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("https://api.aniskip.com/v1/skip-times/%s/%s?types=op&types=ed", url.PathEscape(strconv.Itoa(malID)), url.PathEscape(episode))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
resp, err := doProxiedRequest(ctx, s.httpClient, endpoint, "")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ func rankSources(sources []StreamSource, quality string) ([]sourceScore, error)
|
||||
targetQuality := normalizeQuality(quality)
|
||||
scored := make([]sourceScore, 0, len(filtered))
|
||||
for _, source := range filtered {
|
||||
typeScore := sourceTypePriorityFn(source.Type)
|
||||
providerScore := providerPriorityFn(source.Provider)
|
||||
typeScore := lookupPriority(sourceTypePriority, source.Type, 200)
|
||||
providerScore := lookupPriority(providerPriority, source.Provider, 60)
|
||||
qualityScore := sourceQualityPriority(source.Quality, targetQuality)
|
||||
refererScore := 0
|
||||
if source.Referer != "" {
|
||||
@@ -90,18 +90,11 @@ var sourceQualityDefaults = map[string]int{
|
||||
"auto": 240,
|
||||
}
|
||||
|
||||
func sourceTypePriorityFn(sourceType string) int {
|
||||
if p, ok := sourceTypePriority[strings.ToLower(sourceType)]; ok {
|
||||
func lookupPriority(m map[string]int, key string, fallback int) int {
|
||||
if p, ok := m[strings.ToLower(key)]; ok {
|
||||
return p
|
||||
}
|
||||
return 200
|
||||
}
|
||||
|
||||
func providerPriorityFn(provider string) int {
|
||||
if p, ok := providerPriority[strings.ToLower(provider)]; ok {
|
||||
return p
|
||||
}
|
||||
return 60
|
||||
return fallback
|
||||
}
|
||||
|
||||
func sourceQualityPriority(sourceQuality string, targetQuality string) int {
|
||||
@@ -155,15 +148,15 @@ func parseQualityValue(rawQuality string) int {
|
||||
}
|
||||
|
||||
func extractDigits(value string) string {
|
||||
var digits strings.Builder
|
||||
var digits []byte
|
||||
for _, char := range value {
|
||||
if char >= '0' && char <= '9' {
|
||||
digits.WriteRune(char)
|
||||
} else if digits.Len() > 0 {
|
||||
digits = append(digits, byte(char))
|
||||
} else if len(digits) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return digits.String()
|
||||
return string(digits)
|
||||
}
|
||||
|
||||
func normalizeSourceTypeFromProbe(source StreamSource, contentType string) StreamSource {
|
||||
|
||||
@@ -126,12 +126,12 @@ func normalizeMode(raw string) string {
|
||||
}
|
||||
|
||||
func availableModes(modeSources map[string]ModeSource) []string {
|
||||
preferred := []string{"dub", "sub"}
|
||||
ordered := make([]string, 0, len(modeSources))
|
||||
if _, ok := modeSources["dub"]; ok {
|
||||
ordered = append(ordered, "dub")
|
||||
}
|
||||
if _, ok := modeSources["sub"]; ok {
|
||||
ordered = append(ordered, "sub")
|
||||
for _, mode := range preferred {
|
||||
if _, ok := modeSources[mode]; ok {
|
||||
ordered = append(ordered, mode)
|
||||
}
|
||||
}
|
||||
|
||||
extra := make([]string, 0)
|
||||
|
||||
@@ -29,8 +29,7 @@ func NewRouter(cfg Config) http.Handler {
|
||||
watchlistSvc := watchlist.NewService(cfg.DB, cfg.SQLDB)
|
||||
watchlistHandler := watchlist.NewHandler(watchlistSvc)
|
||||
|
||||
animeSvc := anime.NewService(cfg.JikanClient, cfg.DB)
|
||||
animeHandler := anime.NewHandler(animeSvc)
|
||||
animeHandler := anime.NewHandler(cfg.JikanClient, cfg.DB)
|
||||
playbackSvc := playback.NewService(cfg.DB, cfg.SQLDB, playback.Config{ProxyTokenSecret: cfg.PlaybackProxySecret})
|
||||
playbackHandler := playback.NewHandler(playbackSvc, cfg.JikanClient)
|
||||
|
||||
@@ -60,9 +59,9 @@ func NewRouter(cfg Config) http.Handler {
|
||||
mux.HandleFunc("/studios/", animeHandler.HandleStudioDetails)
|
||||
mux.HandleFunc("/api/studios/", animeHandler.HandleAPIStudioAnime)
|
||||
mux.HandleFunc("/watch/", playbackHandler.HandleWatchPage)
|
||||
mux.HandleFunc("/watch/proxy/stream", playbackHandler.HandleProxyStream)
|
||||
mux.HandleFunc("/watch/proxy/segment", playbackHandler.HandleProxySegment)
|
||||
mux.HandleFunc("/watch/proxy/subtitle", playbackHandler.HandleProxySubtitle)
|
||||
mux.HandleFunc("/watch/proxy/stream", playbackHandler.HandleProxy)
|
||||
mux.HandleFunc("/watch/proxy/segment", playbackHandler.HandleProxy)
|
||||
mux.HandleFunc("/watch/proxy/subtitle", playbackHandler.HandleProxy)
|
||||
mux.HandleFunc("/api/watch-progress", playbackHandler.HandleSaveProgress)
|
||||
mux.HandleFunc("/api/watch-complete", playbackHandler.HandleCompleteAnime)
|
||||
|
||||
@@ -85,7 +84,7 @@ func NewRouter(cfg Config) http.Handler {
|
||||
|
||||
// Wrap mux with global CSRF origin verification and auth checking,
|
||||
// THEN auth context parsing.
|
||||
protectedHandler := middleware.RequireGlobalAuth(middleware.VerifyOrigin(mux))
|
||||
protectedHandler := middleware.RequireGlobalAuthWithPolicy(middleware.NewAccessPolicy())(middleware.VerifyOrigin(mux))
|
||||
authenticatedHandler := middleware.Auth(cfg.AuthService)(protectedHandler)
|
||||
return middleware.RequestLogger(authenticatedHandler)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user