diff --git a/internal/playback/handler/handler.go b/internal/playback/handler/handler.go index 9fb7a40..a271a57 100644 --- a/internal/playback/handler/handler.go +++ b/internal/playback/handler/handler.go @@ -1,6 +1,8 @@ package handler import ( + "fmt" + "io" "log" "mal/internal/domain" "net/http" @@ -20,12 +22,14 @@ func NewPlaybackHandler(svc domain.PlaybackService, animeSvc domain.AnimeService func (h *PlaybackHandler) Register(r *gin.Engine) { log.Println("Registering playback routes") - r.GET("/watch/:id", h.HandleWatchPage) + r.GET("/anime/:id/watch", h.HandleWatchPage) r.POST("/api/watch-progress", h.HandleSaveProgress) + r.GET("/api/watch/thumbnails/:animeId", h.HandleEpisodeThumbnails) + r.GET("/watch/proxy/stream", h.HandleProxyStream) } func (h *PlaybackHandler) HandleWatchPage(c *gin.Context) { - log.Printf("Route /watch/:id triggered for ID: %s", c.Param("id")) + log.Printf("Route /anime/:id/watch triggered for ID: %s", c.Param("id")) id, _ := strconv.Atoi(c.Param("id")) ep := c.DefaultQuery("ep", "1") mode := c.DefaultQuery("mode", "sub") @@ -93,3 +97,90 @@ func (h *PlaybackHandler) HandleSaveProgress(c *gin.Context) { c.Status(http.StatusOK) } + +func (h *PlaybackHandler) HandleEpisodeThumbnails(c *gin.Context) { + id, err := strconv.Atoi(c.Param("animeId")) + if err != nil { + c.Status(http.StatusBadRequest) + return + } + + allEpisodes, err := h.animeSvc.GetAllEpisodes(c.Request.Context(), id) + if err != nil { + log.Printf("failed to fetch thumbnails/episodes: %v", err) + } + + anime, _ := h.animeSvc.GetAnimeByID(c.Request.Context(), id) + if anime.Episodes > 0 && anime.Episodes > len(allEpisodes) { + epMap := make(map[int]domain.EpisodeData) + for _, ep := range allEpisodes { + epMap[ep.MalID] = ep + } + var filled []domain.EpisodeData + for i := 1; i <= anime.Episodes; i++ { + if ep, ok := epMap[i]; ok { + filled = append(filled, ep) + } else { + filled = append(filled, domain.EpisodeData{ + MalID: i, + Title: fmt.Sprintf("Episode %d", i), + }) + } + } + allEpisodes = filled + } + + type Result struct { + MalID int `json:"mal_id"` + Title string `json:"title"` + } + + results := make([]Result, len(allEpisodes)) + for i, ep := range allEpisodes { + results[i] = Result{ + MalID: ep.MalID, + Title: ep.Title, + } + } + + c.JSON(http.StatusOK, results) +} + +func (h *PlaybackHandler) HandleProxyStream(c *gin.Context) { + token := c.Query("token") + if token == "" { + c.Status(http.StatusBadRequest) + return + } + + targetURL, referer, err := h.svc.ResolveProxyToken(token) + if err != nil { + log.Printf("proxy token error: %v", err) + c.Status(http.StatusForbidden) + return + } + + req, err := http.NewRequestWithContext(c.Request.Context(), http.MethodGet, targetURL, nil) + if err != nil { + c.Status(http.StatusBadGateway) + return + } + if referer != "" { + req.Header.Set("Referer", referer) + } + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Printf("proxy fetch error: %v", err) + c.Status(http.StatusBadGateway) + return + } + defer resp.Body.Close() + + for k, v := range resp.Header { + c.Header(k, v[0]) + } + c.Status(resp.StatusCode) + io.Copy(c.Writer, resp.Body) +}