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