From ffd67338c3c9f2d36d7e9e9bd3cfa92b5dfdfadc Mon Sep 17 00:00:00 2001 From: mkelvers Date: Mon, 20 Apr 2026 01:42:09 +0200 Subject: [PATCH] refactor(playback): simplify handlers, http utils, and ranking --- internal/features/playback/allanime_client.go | 23 ++++++++--------- internal/features/playback/handler.go | 12 --------- internal/features/playback/http_utils.go | 25 +++++++++++++++++++ internal/features/playback/progress.go | 17 ++----------- .../features/playback/provider_extractor.go | 22 ++-------------- internal/features/playback/proxy_security.go | 18 ++++++------- internal/features/playback/service_base.go | 10 ++------ internal/features/playback/service_http.go | 8 +----- internal/features/playback/service_ranking.go | 25 +++++++------------ .../features/playback/service_resolution.go | 10 ++++---- internal/server/routes.go | 11 ++++---- 11 files changed, 71 insertions(+), 110 deletions(-) create mode 100644 internal/features/playback/http_utils.go diff --git a/internal/features/playback/allanime_client.go b/internal/features/playback/allanime_client.go index 7c93aa6..399bd9b 100644 --- a/internal/features/playback/allanime_client.go +++ b/internal/features/playback/allanime_client.go @@ -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 } diff --git a/internal/features/playback/handler.go b/internal/features/playback/handler.go index 0b349e7..3a3f699 100644 --- a/internal/features/playback/handler.go +++ b/internal/features/playback/handler.go @@ -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) diff --git a/internal/features/playback/http_utils.go b/internal/features/playback/http_utils.go new file mode 100644 index 0000000..a8ef073 --- /dev/null +++ b/internal/features/playback/http_utils.go @@ -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 +} diff --git a/internal/features/playback/progress.go b/internal/features/playback/progress.go index 2330f53..2babb15 100644 --- a/internal/features/playback/progress.go +++ b/internal/features/playback/progress.go @@ -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 -} diff --git a/internal/features/playback/provider_extractor.go b/internal/features/playback/provider_extractor.go index b7ce18f..c46e545 100644 --- a/internal/features/playback/provider_extractor.go +++ b/internal/features/playback/provider_extractor.go @@ -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 } diff --git a/internal/features/playback/proxy_security.go b/internal/features/playback/proxy_security.go index a494e43..5559a9d 100644 --- a/internal/features/playback/proxy_security.go +++ b/internal/features/playback/proxy_security.go @@ -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) { diff --git a/internal/features/playback/service_base.go b/internal/features/playback/service_base.go index 58bb6e4..ffb1d8b 100644 --- a/internal/features/playback/service_base.go +++ b/internal/features/playback/service_base.go @@ -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) -} diff --git a/internal/features/playback/service_http.go b/internal/features/playback/service_http.go index 7515a0e..63022b5 100644 --- a/internal/features/playback/service_http.go +++ b/internal/features/playback/service_http.go @@ -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 } diff --git a/internal/features/playback/service_ranking.go b/internal/features/playback/service_ranking.go index 12b5477..ff28f0a 100644 --- a/internal/features/playback/service_ranking.go +++ b/internal/features/playback/service_ranking.go @@ -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 { diff --git a/internal/features/playback/service_resolution.go b/internal/features/playback/service_resolution.go index 0172c63..77c77d5 100644 --- a/internal/features/playback/service_resolution.go +++ b/internal/features/playback/service_resolution.go @@ -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) diff --git a/internal/server/routes.go b/internal/server/routes.go index b6608fb..5e61b5b 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -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) }