refactor: consolidate proxy handlers and simplify ranking/watchlist
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -184,72 +185,48 @@ func convertSegments(segments []SkipSegment) []templates.SkipSegment {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) HandleProxyStream(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mode := normalizeMode(r.URL.Query().Get("mode"))
|
|
||||||
if mode == "" {
|
|
||||||
mode = "dub"
|
|
||||||
}
|
|
||||||
|
|
||||||
token := strings.TrimSpace(r.URL.Query().Get("token"))
|
token := strings.TrimSpace(r.URL.Query().Get("token"))
|
||||||
if token == "" {
|
if token == "" {
|
||||||
http.Error(w, "missing playback token", http.StatusBadRequest)
|
http.Error(w, "missing playback token", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetURL, referer, err := h.svc.resolveProxyToken(r.Context(), token, proxyScopeStream)
|
scope := proxyScope(strings.TrimPrefix(r.URL.Path, "/watch/proxy/"))
|
||||||
|
scopeLabel := map[proxyScope]string{
|
||||||
|
proxyScopeStream: "stream",
|
||||||
|
proxyScopeSegment: "segment",
|
||||||
|
proxyScopeSubtitle: "subtitle",
|
||||||
|
}[scope]
|
||||||
|
if scopeLabel == "" {
|
||||||
|
http.Error(w, "invalid proxy scope", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetURL, referer, err := h.svc.resolveProxyToken(r.Context(), token, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "invalid stream token", http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("invalid %s token", scopeLabel), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func (h *Handler) HandleProxySegment(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
h.HandleProxy(w, r)
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token := strings.TrimSpace(r.URL.Query().Get("token"))
|
|
||||||
if token == "" {
|
|
||||||
http.Error(w, "missing segment token", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targetURL, referer, err := h.svc.resolveProxyToken(r.Context(), token, proxyScopeSegment)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "invalid segment token", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.proxyUpstream(w, r, targetURL, referer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) HandleProxySubtitle(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleProxySubtitle(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
h.HandleProxy(w, r)
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token := strings.TrimSpace(r.URL.Query().Get("token"))
|
|
||||||
if token == "" {
|
|
||||||
http.Error(w, "missing subtitle token", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targetURL, referer, err := h.svc.resolveProxyToken(r.Context(), token, proxyScopeSubtitle)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "invalid subtitle token", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.proxyUpstream(w, r, targetURL, referer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) HandleSaveProgress(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleSaveProgress(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -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 := sourceTypePriority(source.Type)
|
typeScore := sourceTypePriorityFn(source.Type)
|
||||||
providerScore := providerPriority(source.Provider)
|
providerScore := providerPriorityFn(source.Provider)
|
||||||
qualityScore := sourceQualityPriority(source.Quality, targetQuality)
|
qualityScore := sourceQualityPriority(source.Quality, targetQuality)
|
||||||
refererScore := 0
|
refererScore := 0
|
||||||
if source.Referer != "" {
|
if source.Referer != "" {
|
||||||
@@ -65,48 +65,43 @@ func normalizeQuality(quality string) string {
|
|||||||
return lower
|
return lower
|
||||||
}
|
}
|
||||||
|
|
||||||
func sourceTypePriority(sourceType string) int {
|
var sourceTypePriority = map[string]int{
|
||||||
switch strings.ToLower(sourceType) {
|
"mp4": 500,
|
||||||
case "mp4":
|
"m3u8": 450,
|
||||||
return 500
|
"unknown": 300,
|
||||||
case "m3u8":
|
"embed": 100,
|
||||||
return 450
|
|
||||||
case "unknown":
|
|
||||||
return 300
|
|
||||||
case "embed":
|
|
||||||
return 100
|
|
||||||
default:
|
|
||||||
return 200
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func providerPriority(provider string) int {
|
var providerPriority = map[string]int{
|
||||||
switch strings.ToLower(provider) {
|
"s-mp4": 120,
|
||||||
case "s-mp4":
|
"default": 115,
|
||||||
return 120
|
"luf-mp4": 110,
|
||||||
case "default":
|
"vid-mp4": 105,
|
||||||
return 115
|
"yt-mp4": 100,
|
||||||
case "luf-mp4":
|
"mp4": 95,
|
||||||
return 110
|
"uv-mp4": 90,
|
||||||
case "vid-mp4":
|
"hls": 80,
|
||||||
return 105
|
"sw": 40,
|
||||||
case "yt-mp4":
|
"ok": 35,
|
||||||
return 100
|
"ss-hls": 30,
|
||||||
case "mp4":
|
}
|
||||||
return 95
|
|
||||||
case "uv-mp4":
|
var sourceQualityDefaults = map[string]int{
|
||||||
return 90
|
"auto": 240,
|
||||||
case "hls":
|
}
|
||||||
return 80
|
|
||||||
case "sw":
|
func sourceTypePriorityFn(sourceType string) int {
|
||||||
return 40
|
if p, ok := sourceTypePriority[strings.ToLower(sourceType)]; ok {
|
||||||
case "ok":
|
return p
|
||||||
return 35
|
|
||||||
case "ss-hls":
|
|
||||||
return 30
|
|
||||||
default:
|
|
||||||
return 60
|
|
||||||
}
|
}
|
||||||
|
return 200
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
@@ -126,34 +121,6 @@ func sourceQualityPriority(sourceQuality string, targetQuality string) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseQualityValue(rawQuality string) int {
|
|
||||||
lower := strings.ToLower(rawQuality)
|
|
||||||
var digits strings.Builder
|
|
||||||
|
|
||||||
for _, char := range lower {
|
|
||||||
if char >= '0' && char <= '9' {
|
|
||||||
digits.WriteRune(char)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if digits.Len() > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if digits.Len() > 0 {
|
|
||||||
value, err := strconv.Atoi(digits.String())
|
|
||||||
if err == nil {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if lower == "auto" {
|
|
||||||
return 240
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func qualityMatches(sourceQuality string, targetQuality string) bool {
|
func qualityMatches(sourceQuality string, targetQuality string) bool {
|
||||||
sourceLower := strings.ToLower(sourceQuality)
|
sourceLower := strings.ToLower(sourceQuality)
|
||||||
targetLower := strings.ToLower(targetQuality)
|
targetLower := strings.ToLower(targetQuality)
|
||||||
@@ -166,10 +133,25 @@ func qualityMatches(sourceQuality string, targetQuality string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceDigits := extractDigits(sourceLower)
|
return extractDigits(sourceLower) == extractDigits(targetLower)
|
||||||
targetDigits := extractDigits(targetLower)
|
}
|
||||||
|
|
||||||
return sourceDigits != "" && sourceDigits == targetDigits
|
func parseQualityValue(rawQuality string) int {
|
||||||
|
lower := strings.ToLower(rawQuality)
|
||||||
|
if lower == "auto" {
|
||||||
|
return 240
|
||||||
|
}
|
||||||
|
|
||||||
|
digits := extractDigits(lower)
|
||||||
|
if digits == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.Atoi(digits)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractDigits(value string) string {
|
func extractDigits(value string) string {
|
||||||
@@ -177,13 +159,10 @@ func extractDigits(value string) string {
|
|||||||
for _, char := range value {
|
for _, char := range value {
|
||||||
if char >= '0' && char <= '9' {
|
if char >= '0' && char <= '9' {
|
||||||
digits.WriteRune(char)
|
digits.WriteRune(char)
|
||||||
continue
|
} else if digits.Len() > 0 {
|
||||||
}
|
|
||||||
if digits.Len() > 0 {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return digits.String()
|
return digits.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,24 +133,23 @@ func (s *Service) DeleteContinueWatching(ctx context.Context, userID string, ani
|
|||||||
return ErrInvalidAnimeID
|
return ErrInvalidAnimeID
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.sqlDB == nil {
|
params := database.DeleteContinueWatchingEntryParams{
|
||||||
if err := s.db.DeleteContinueWatchingEntry(ctx, database.DeleteContinueWatchingEntryParams{
|
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
AnimeID: animeID,
|
AnimeID: animeID,
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("failed to delete continue watching entry: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.db.SaveWatchProgress(ctx, database.SaveWatchProgressParams{
|
clearProgress := database.SaveWatchProgressParams{
|
||||||
CurrentEpisode: sql.NullInt64{Valid: false},
|
CurrentEpisode: sql.NullInt64{Valid: false},
|
||||||
CurrentTimeSeconds: 0,
|
CurrentTimeSeconds: 0,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
AnimeID: animeID,
|
AnimeID: animeID,
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("failed to clear watchlist progress: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if s.sqlDB == nil {
|
||||||
|
if err := s.db.DeleteContinueWatchingEntry(ctx, params); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete continue watching entry: %w", err)
|
||||||
|
}
|
||||||
|
return s.db.SaveWatchProgress(ctx, clearProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := s.sqlDB.BeginTx(ctx, nil)
|
tx, err := s.sqlDB.BeginTx(ctx, nil)
|
||||||
@@ -160,27 +159,14 @@ func (s *Service) DeleteContinueWatching(ctx context.Context, userID string, ani
|
|||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
txQueries := database.New(tx)
|
txQueries := database.New(tx)
|
||||||
if err := txQueries.DeleteContinueWatchingEntry(ctx, database.DeleteContinueWatchingEntryParams{
|
if err := txQueries.DeleteContinueWatchingEntry(ctx, params); err != nil {
|
||||||
UserID: userID,
|
|
||||||
AnimeID: animeID,
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("failed to delete continue watching entry: %w", err)
|
return fmt.Errorf("failed to delete continue watching entry: %w", err)
|
||||||
}
|
}
|
||||||
|
if err := txQueries.SaveWatchProgress(ctx, clearProgress); err != nil {
|
||||||
if err := txQueries.SaveWatchProgress(ctx, database.SaveWatchProgressParams{
|
|
||||||
CurrentEpisode: sql.NullInt64{Valid: false},
|
|
||||||
CurrentTimeSeconds: 0,
|
|
||||||
UserID: userID,
|
|
||||||
AnimeID: animeID,
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("failed to clear watchlist progress: %w", err)
|
return fmt.Errorf("failed to clear watchlist progress: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
return tx.Commit()
|
||||||
return fmt.Errorf("failed to commit continue watching deletion: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExportEntry struct {
|
type ExportEntry struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user