diff --git a/api/anime/handler.go b/api/anime/handler.go index 1b8d753..4426a27 100644 --- a/api/anime/handler.go +++ b/api/anime/handler.go @@ -291,7 +291,100 @@ func (h *Handler) HandleQuickSearch(w http.ResponseWriter, r *http.Request) { } func (h *Handler) HandleDiscover(w http.ResponseWriter, r *http.Request) { - renderNotFoundPage(r, w) + trending, err := h.jikanClient.GetSeasonsNow(r.Context(), 1) + if err != nil { + log.Printf("seasons now error: %v", err) + } + + upcoming, err := h.jikanClient.GetSeasonsUpcoming(r.Context(), 1) + if err != nil { + log.Printf("seasons upcoming error: %v", err) + } + + top, err := h.jikanClient.GetTopAnime(r.Context(), 1) + if err != nil { + log.Printf("top anime error: %v", err) + } + + seen := make(map[int]bool) + uniqueTrending := make([]jikan.Anime, 0) + for _, a := range trending.Animes { + if !seen[a.MalID] { + seen[a.MalID] = true + uniqueTrending = append(uniqueTrending, a) + } + if len(uniqueTrending) >= 10 { + break + } + } + + uniqueUpcoming := make([]jikan.Anime, 0) + for _, a := range upcoming.Animes { + if !seen[a.MalID] { + seen[a.MalID] = true + uniqueUpcoming = append(uniqueUpcoming, a) + } + if len(uniqueUpcoming) >= 10 { + break + } + } + + uniqueTop := make([]jikan.Anime, 0) + for _, a := range top.Animes { + if !seen[a.MalID] { + seen[a.MalID] = true + uniqueTop = append(uniqueTop, a) + } + if len(uniqueTop) >= 10 { + break + } + } + + user, _ := r.Context().Value(ctxpkg.UserKey).(*database.User) + watchlistMap := make(map[int64]bool) + var watchlistIDs []int64 + if user != nil { + watchlist, _ := h.db.GetUserWatchList(r.Context(), user.ID) + watchlistIDs = make([]int64, len(watchlist)) + for i, entry := range watchlist { + watchlistMap[entry.AnimeID] = true + watchlistIDs[i] = entry.AnimeID + } + } + + if err := templates.GetRenderer().ExecuteTemplate(w, "discover.gohtml", map[string]any{ + "User": user, + "CurrentPath": r.URL.Path, + "Trending": uniqueTrending, + "Upcoming": uniqueUpcoming, + "Top": uniqueTop, + "WatchlistMap": watchlistMap, + "WatchlistIDs": watchlistIDs, + }); err != nil { + log.Printf("render error: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } +} + +func (h *Handler) HandleRandomAnime(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + anime, err := h.jikanClient.GetRandomAnime(r.Context()) + if err != nil { + log.Printf("random anime error: %v", err) + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]string{"error": "Failed to fetch random anime"}) + return + } + + if anime.MalID == 0 { + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{"error": "No anime found"}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]any{"data": anime}) } func (h *Handler) HandleAPIDiscoverAiring(w http.ResponseWriter, r *http.Request) { diff --git a/integrations/jikan/seasons.go b/integrations/jikan/seasons.go index 79af700..87ea76b 100644 --- a/integrations/jikan/seasons.go +++ b/integrations/jikan/seasons.go @@ -83,3 +83,17 @@ func (c *Client) GetSeasonsUpcoming(ctx context.Context, page int) (TopAnimeResu HasNextPage: result.Pagination.HasNextPage, }, nil } + +func (c *Client) GetRandomAnime(ctx context.Context) (Anime, error) { + var result struct { + Data Anime `json:"data"` + } + + reqURL := fmt.Sprintf("%s/random/anime", c.baseURL) + err := c.fetchWithRetry(ctx, reqURL, &result) + if err != nil { + return Anime{}, err + } + + return result.Data, nil +} diff --git a/internal/server/routes.go b/internal/server/routes.go index b1a12d6..ac09018 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -69,7 +69,9 @@ func NewRouter(cfg Config) http.Handler { mux.HandleFunc("/", animeHandler.HandleCatalog) mux.HandleFunc("/search", animeHandler.HandleSearch) mux.HandleFunc("/browse", animeHandler.HandleBrowse) + mux.HandleFunc("/discover", animeHandler.HandleDiscover) mux.HandleFunc("/api/search-quick", animeHandler.HandleQuickSearch) + mux.HandleFunc("/api/jikan/random/anime", animeHandler.HandleRandomAnime) mux.HandleFunc("/anime/", func(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/watch") { playbackHandler.HandleWatchPage(w, r) diff --git a/static/dedupe.ts b/static/dedupe.ts index 4fba844..9a8c125 100644 --- a/static/dedupe.ts +++ b/static/dedupe.ts @@ -1,13 +1,12 @@ const dedupe = (): void => { - const script = document.currentScript as HTMLScriptElement | null - if (!script) return - const containerId = script.getAttribute('data-container') - const container = containerId ? document.getElementById(containerId) : document - if (!container) return + console.log('Dedupe running...') const seen = new Set() - container.querySelectorAll('[data-id]').forEach((item) => { + const elements = document.querySelectorAll('[data-id]') + console.log('Found elements:', elements.length) + elements.forEach((item) => { const id = item.getAttribute('data-id') if (id && seen.has(id)) { + console.log('Removing duplicate:', id) item.remove() } else if (id) { seen.add(id) @@ -15,4 +14,10 @@ const dedupe = (): void => { }) } -dedupe() +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', dedupe) +} else { + dedupe() +} +// Also run on window load to be sure +window.addEventListener('load', dedupe) diff --git a/templates/base.gohtml b/templates/base.gohtml index 2e1bb0c..2a60db9 100644 --- a/templates/base.gohtml +++ b/templates/base.gohtml @@ -14,6 +14,7 @@ + {{end}} + + +
+
+
+
+

+ Don't know what to watch? +

+

+ Let us pick something for you from our vast collection of anime. +

+ +
+
+ +
+
+

Trending This Season

+ View all +
+
+ {{range $i, $anime := .Trending}} +
+ {{template "anime_card" dict "Anime" $anime "WithActions" true "IsWatchlist" (index $.WatchlistMap $anime.MalID)}} +
+ {{end}} +
+
+ +
+
+

Highly Anticipated

+ View all +
+
+ {{range $i, $anime := .Upcoming}} +
+ {{template "anime_card" dict "Anime" $anime "WithActions" true "IsWatchlist" (index $.WatchlistMap $anime.MalID)}} +
+ {{end}} +
+
+ +
+
+

All-Time Greats

+ View all +
+
+ {{range $i, $anime := .Top}} +
+ {{template "anime_card" dict "Anime" $anime "WithActions" true "IsWatchlist" (index $.WatchlistMap $anime.MalID)}} +
+ {{end}} +
+
+
+ + +{{end}} \ No newline at end of file