package recommended import ( "net/http" "sort" "github.com/aerogo/aero" "github.com/animenotifier/arn" "github.com/animenotifier/notify.moe/components" "github.com/animenotifier/notify.moe/utils" ) const ( maxRecommendations = 20 worstGenreCount = 5 ) // Anime shows a list of recommended anime. func Anime(ctx *aero.Context) string { user := utils.GetUser(ctx) nick := ctx.Get("nick") viewUser, err := arn.GetUserByNick(nick) if err != nil { return ctx.Error(http.StatusUnauthorized, "Not logged in", err) } animeList := viewUser.AnimeList() genreItems := animeList.Genres() genreAffinity := map[string]float64{} worstGenres := []string{} 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 worstGenres = append(worstGenres, genre) } sort.Slice(worstGenres, func(i, j int) bool { return genreAffinity[worstGenres[i]] < genreAffinity[worstGenres[j]] }) if len(worstGenres) > worstGenreCount { worstGenres = worstGenres[:worstGenreCount] } // 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 that are upcoming or tba if anime.Status == "upcoming" || anime.Status == "tba" { continue } // Skip anime from my list (except planned anime) existing := animeList.Find(anime.ID) if existing != nil && existing.Status != arn.AnimeListStatusPlanned { continue } // Skip anime that don't have one of the top genres for that user worstGenreFound := false for _, genre := range anime.Genres { if arn.Contains(worstGenres, genre) { worstGenreFound = true break } } if worstGenreFound { continue } animeAffinity := 0.0 // Planned anime go higher if existing != nil && existing.Status == arn.AnimeListStatusPlanned { animeAffinity += 75.0 } animeAffinity += float64(anime.Popularity.Total()) animeAffinity += anime.Rating.Overall * 80 affinity[anime.ID] = animeAffinity } // Sort sort.Slice(recommendations, func(i, j int) bool { affinityA := affinity[recommendations[i].ID] affinityB := affinity[recommendations[j].ID] if affinityA == affinityB { return recommendations[i].Title.Canonical < recommendations[j].Title.Canonical } return affinityA > affinityB }) // Take the top 10 if len(recommendations) > maxRecommendations { recommendations = recommendations[:maxRecommendations] } return ctx.HTML(components.RecommendedAnime(recommendations, worstGenres, viewUser, user)) }