diff --git a/pages/index.go b/pages/index.go index 5f8130b9..ad7db770 100644 --- a/pages/index.go +++ b/pages/index.go @@ -42,6 +42,7 @@ import ( "github.com/animenotifier/notify.moe/pages/popular" "github.com/animenotifier/notify.moe/pages/posts" "github.com/animenotifier/notify.moe/pages/profile" + "github.com/animenotifier/notify.moe/pages/recommended" "github.com/animenotifier/notify.moe/pages/search" "github.com/animenotifier/notify.moe/pages/settings" "github.com/animenotifier/notify.moe/pages/shop" @@ -147,6 +148,7 @@ func Configure(app *aero.Application) { l.Page("/user/:nick/animelist/hold", animelist.FilterByStatus(arn.AnimeListStatusHold)) l.Page("/user/:nick/animelist/dropped", animelist.FilterByStatus(arn.AnimeListStatusDropped)) l.Page("/user/:nick/animelist/anime/:id", animelistitem.Get) + l.Page("/user/:nick/recommended/anime", recommended.Anime) // Anime list l.Page("/animelist/watching", home.FilterByStatus(arn.AnimeListStatusWatching)) diff --git a/pages/recommended/anime.go b/pages/recommended/anime.go new file mode 100644 index 00000000..9760544f --- /dev/null +++ b/pages/recommended/anime.go @@ -0,0 +1,89 @@ +package recommended + +import ( + "net/http" + "sort" + + "github.com/aerogo/aero" + "github.com/animenotifier/arn" + "github.com/animenotifier/notify.moe/components" +) + +const maxRecommendations = 20 + +// Anime shows a list of recommended anime. +func Anime(ctx *aero.Context) string { + nick := ctx.Get("nick") + user, err := arn.GetUserByNick(nick) + + if err != nil { + return ctx.Error(http.StatusUnauthorized, "Not logged in", err) + } + + animeList := user.AnimeList() + genreItems := animeList.Genres() + genreAffinity := map[string]float64{} + + for genre, animeListItems := range genreItems { + affinity := 0.0 + + for _, item := range animeListItems { + if item.Status == arn.AnimeListStatusDropped { + affinity -= 5.0 + continue + } + + if item.Rating.Overall != 0 { + affinity += item.Rating.Overall + } else { + affinity += 5.0 + } + } + + genreAffinity[genre] = affinity + } + + // Get all anime + recommendations := arn.AllAnime() + + // Affinity maps an anime ID to a number that indicates how likely a user is going to enjoy that anime. + affinity := map[string]float64{} + + // Calculate affinity for each anime + for _, anime := range recommendations { + // Skip anime from my list (except planned anime) + existing := animeList.Find(anime.ID) + + if existing != nil && existing.Status != arn.AnimeListStatusPlanned { + continue + } + + affinity[anime.ID] = float64(anime.Popularity.Total()) + + // animeGenresAffinity := 0.0 + + // if len(anime.Genres) > 0 { + // for _, genre := range anime.Genres { + // if genreAffinity[genre] > animeGenresAffinity { + // animeGenresAffinity = genreAffinity[genre] + // } + // } + + // animeGenresAffinity = animeGenresAffinity / float64(len(anime.Genres)) + // } + + // affinity[anime.ID] = animeGenresAffinity + } + + // Sort + sort.Slice(recommendations, func(i, j int) bool { + return affinity[recommendations[i].ID] > affinity[recommendations[j].ID] + }) + + // Take the top 10 + if len(recommendations) > maxRecommendations { + recommendations = recommendations[:maxRecommendations] + } + + return ctx.HTML(components.RecommendedAnime(recommendations, user)) +} diff --git a/pages/recommended/anime.pixy b/pages/recommended/anime.pixy new file mode 100644 index 00000000..7a526cbd --- /dev/null +++ b/pages/recommended/anime.pixy @@ -0,0 +1,7 @@ +component RecommendedAnime(animes []*arn.Anime, user *arn.User) + h1= "Recommended anime for " + user.Nick + + AnimeGrid(animes, user) + //- ul + //- each anime in animes + //- li= anime.Title.Canonical \ No newline at end of file