func ShowEpisodes(ctx *gin.Context) { showId, _ := strconv.Atoi(ctx.Params.ByName("showId")) seasonNumber, _ := strconv.Atoi(ctx.Params.ByName("season")) language := config.Get().Language show := tmdb.GetShow(showId, language) season := tmdb.GetSeason(showId, seasonNumber, language) items := season.Episodes.ToListItems(show, season) for _, item := range items { playUrl := UrlForXBMC("/show/%d/season/%d/episode/%d/play", show.Id, seasonNumber, item.Info.Episode, ) episodeLinksUrl := UrlForXBMC("/show/%d/season/%d/episode/%d/links", show.Id, seasonNumber, item.Info.Episode, ) if config.Get().ChooseStreamAuto == true { item.Path = playUrl } else { item.Path = episodeLinksUrl } item.ContextMenu = [][]string{ []string{"LOCALIZE[30202]", fmt.Sprintf("XBMC.PlayMedia(%s)", episodeLinksUrl)}, []string{"LOCALIZE[30023]", fmt.Sprintf("XBMC.PlayMedia(%s)", playUrl)}, []string{"LOCALIZE[30203]", "XBMC.Action(Info)"}, []string{"LOCALIZE[30037]", fmt.Sprintf("XBMC.RunPlugin(%s)", UrlForXBMC("/setviewmode/episodes"))}, } item.IsPlayable = true } ctx.JSON(200, xbmc.NewView("episodes", items)) }
func MoviesMostVoted(ctx *gin.Context) { page := -1 if config.Get().EnablePaging == true { currentpage, err := strconv.Atoi(ctx.DefaultQuery("page", "0")) if err == nil { page = currentpage } } renderMovies(tmdb.MostVotedMoviesComplete("", config.Get().Language, page), ctx, page) }
func TopRatedShows(ctx *gin.Context) { page := -1 if config.Get().EnablePagination == true { currentpage, err := strconv.Atoi(ctx.DefaultQuery("page", "0")) if err == nil { page = currentpage } } renderShows(tmdb.TopRatedShowsComplete("", config.Get().Language, page), ctx, page) }
func TopRatedMovies(ctx *gin.Context) { genre := ctx.Params.ByName("genre") if genre == "0" { genre = "" } page := -1 if config.Get().EnablePaging == true { currentpage, err := strconv.Atoi(ctx.DefaultQuery("page", "0")) if err == nil { page = currentpage } } renderMovies(tmdb.TopRatedMoviesComplete(genre, config.Get().Language, page), ctx, page) }
func MakeQuasarRepositoryAddon() error { addonId := "repository.quasar" addonName := "Quasar Repository" quasarHost := fmt.Sprintf("http://localhost:%d", config.ListenPort) addon := &xbmc.Addon{ Id: addonId, Name: addonName, Version: util.Version, ProviderName: config.Get().Info.Author, Extensions: []*xbmc.AddonExtension{ &xbmc.AddonExtension{ Point: "xbmc.addon.repository", Name: addonName, Info: &xbmc.AddonRepositoryInfo{ Text: quasarHost + "/repository/scakemyer/plugin.video.quasar/addons.xml", Compressed: false, }, Checksum: quasarHost + "/repository/scakemyer/plugin.video.quasar/addons.xml.md5", Datadir: &xbmc.AddonRepositoryDataDir{ Text: quasarHost + "/repository/scakemyer/", Zip: true, }, }, &xbmc.AddonExtension{ Point: "xbmc.addon.metadata", Summaries: []*xbmc.AddonText{ &xbmc.AddonText{"GitHub repository for Quasar Updates", "en"}, }, Platform: "all", }, }, } addonPath := filepath.Clean(filepath.Join(config.Get().Info.Path, "..", addonId)) if err := os.MkdirAll(addonPath, 0777); err != nil { return err } if err := copyFile(filepath.Join(config.Get().Info.Path, "icon.png"), filepath.Join(addonPath, "icon.png")); err != nil { return err } addonXmlFile, err := os.Create(filepath.Join(addonPath, "addon.xml")) if err != nil { return err } defer addonXmlFile.Close() return xml.NewEncoder(addonXmlFile).Encode(addon) }
func PlayShow(ctx *gin.Context) { if config.Get().ChooseStreamAuto == true { ShowEpisodePlay(ctx) } else { ShowEpisodeLinks(ctx) } }
func RemoveShow(ctx *gin.Context) { LibraryPath := config.Get().LibraryPath ShowsLibraryPath := filepath.Join(LibraryPath, "Shows") DBPath := filepath.Join(LibraryPath, fmt.Sprintf("%s.json", DBName)) showId := ctx.Params.ByName("showId") show, err := tvdb.NewShow(showId, "en") if err != nil { ctx.String(404, "") return } ShowPath := filepath.Join(ShowsLibraryPath, toFileName(show.SeriesName)) if err := RemoveFromJsonDB(DBPath, showId, LShow); err != nil { libraryLog.Info("Unable to remove show from db") ctx.String(404, "") return } if err := os.RemoveAll(ShowPath); err != nil { libraryLog.Info("Unable to remove show folder") ctx.String(404, "") return } xbmc.Notify("Quasar", "LOCALIZE[30222]", config.AddonIcon()) ctx.String(200, "") xbmc.VideoLibraryClean() libraryLog.Info("Show removed") }
func PlayMovie(ctx *gin.Context) { if config.Get().ChooseStreamAuto == true { MoviePlay(ctx) } else { MovieLinks(ctx) } }
func CheckApiKey() { tmdbLog.Info("Checking TMDB API key...") customApiKey := config.Get().TMDBApiKey if customApiKey != "" { apiKeys = append(apiKeys, customApiKey) apiKey = customApiKey } result := false for index := len(apiKeys); index >= 0; index-- { result = tmdbCheck(apiKey) if result { tmdbLog.Noticef("TMDB API key check passed, using %s...", apiKey[:7]) break } else { tmdbLog.Warningf("TMDB API key failed: %s", apiKey) if apiKey == apiKeys[index] { apiKeys = append(apiKeys[:index], apiKeys[index+1:]...) } apiKey = apiKeys[rand.Int()%len(apiKeys)] } } if result == false { tmdbLog.Error("No valid TMDB API key found") } }
func GetCount(ctx *gin.Context) { LibraryPath := config.Get().LibraryPath DBPath := filepath.Join(LibraryPath, fmt.Sprintf("%s.json", DBName)) var db DataBase if _, err := os.Stat(DBPath); err == nil { file, err := ioutil.ReadFile(DBPath) if err != nil { ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") ctx.JSON(200, gin.H{ "success": false, }) return } json.Unmarshal(file, &db) } ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") ctx.JSON(200, gin.H{ "success": true, "movies": len(db.Movies), "shows": len(db.Shows), "total": len(db.Movies) + len(db.Shows), }) }
func GetSurgeClearances() { header := http.Header{ "Content-type": []string{"application/json"}, } params := napping.Params{}.AsUrlValues() req := napping.Request{ Url: fmt.Sprintf("%s/%s", "https://cloudhole.surge.sh", "cloudhole.json"), Method: "GET", Params: ¶ms, Header: &header, } resp, err := napping.Send(&req) var tmpClearances []*Clearance if err == nil && resp.Status() == 200 { resp.Unmarshal(&tmpClearances) } apiKey := config.Get().CloudHoleKey for _, clearance := range tmpClearances { if clearance.Key == apiKey { clearances = append(clearances, clearance) } } }
func getMovieById(movieId string, language string) *Movie { var movie *Movie cacheStore := cache.NewFileStore(path.Join(config.Get().ProfilePath, "cache")) key := fmt.Sprintf("com.tmdb.movie.%s.%s", movieId, language) if err := cacheStore.Get(key, &movie); err != nil { rateLimiter.Call(func() { urlValues := napping.Params{ "api_key": apiKey, "append_to_response": "credits,images,alternative_titles,translations,external_ids,trailers", "language": language, }.AsUrlValues() napping.Get( tmdbEndpoint+"movie/"+movieId, &urlValues, &movie, nil, ) if movie != nil { cacheStore.Set(key, movie, cacheTime) } }) } if movie == nil { return nil } switch t := movie.RawPopularity.(type) { case string: popularity, _ := strconv.ParseFloat(t, 64) movie.Popularity = popularity case float64: movie.Popularity = t } return movie }
func SearchMovies(query string, page string) (movies []*Movies, err error) { endPoint := "search" params := napping.Params{ "page": page, "limit": strconv.Itoa(config.Get().ResultsPerPage), "query": query, "extended": "full,images", }.AsUrlValues() resp, err := Get(endPoint, params) if err != nil { return movies, err } else if resp.Status() != 200 { return movies, errors.New(fmt.Sprintf("SearchMovies bad status: %d", resp.Status())) } // TODO use response headers for pagination limits: // X-Pagination-Page-Count:10 // X-Pagination-Item-Count:100 resp.Unmarshal(&movies) return movies, err }
func Play(btService *bittorrent.BTService) gin.HandlerFunc { return func(ctx *gin.Context) { uri := ctx.Request.URL.Query().Get("uri") if uri == "" { return } fileIndex := -1 index := ctx.Request.URL.Query().Get("index") if index != "" { fIndex, err := strconv.Atoi(index) if err == nil { fileIndex = fIndex } } torrent := bittorrent.NewTorrent(uri) magnet := torrent.Magnet() boosters := url.Values{ "tr": providers.DefaultTrackers, } magnet += "&" + boosters.Encode() player := bittorrent.NewBTPlayer(btService, magnet, config.Get().KeepFilesAfterStop == false, fileIndex) if player.Buffer() != nil { return } hostname := "localhost" if localIP, err := util.LocalIP(); err == nil { hostname = localIP.String() } rUrl, _ := url.Parse(fmt.Sprintf("http://%s:%d/files/%s", hostname, config.ListenPort, player.PlayURL())) ctx.Redirect(302, rUrl.String()) } }
func TopShows(topCategory string, page string) (shows []*Shows, err error) { endPoint := "shows/" + topCategory params := napping.Params{ "page": page, "limit": strconv.Itoa(config.Get().ResultsPerPage), "extended": "full,images", }.AsUrlValues() resp, err := Get(endPoint, params) if err != nil { return shows, err } else if resp.Status() != 200 { return shows, errors.New(fmt.Sprintf("TopShows bad status: %d", resp.Status())) } if topCategory == "popular" { var showList []*Show resp.Unmarshal(&showList) showListing := make([]*Shows, 0) for _, show := range showList { showItem := Shows{ Show: show, } showListing = append(showListing, &showItem) } shows = showListing } else { resp.Unmarshal(&shows) } return shows, err }
func Post(endPoint string, payload *bytes.Buffer) (resp *napping.Response, err error) { header := http.Header{ "Content-type": []string{"application/json"}, "Authorization": []string{fmt.Sprintf("Bearer %s", config.Get().TraktToken)}, "trakt-api-key": []string{ClientId}, "trakt-api-version": []string{ApiVersion}, "User-Agent": []string{clearance.UserAgent}, "Cookie": []string{clearance.Cookies}, } req := napping.Request{ Url: fmt.Sprintf("%s/%s", ApiUrl, endPoint), Method: "POST", RawPayload: true, Payload: payload, Header: &header, } resp, err = napping.Send(&req) if err == nil && resp.Status() == 403 && retries < 3 { retries += 1 err = newClearance() if err == nil { resp, err = Post(endPoint, payload) } } return resp, err }
func GetWithAuth(endPoint string, params url.Values) (resp *napping.Response, err error) { header := http.Header{ "Content-type": []string{"application/json"}, "Authorization": []string{fmt.Sprintf("Bearer %s", config.Get().TraktToken)}, "trakt-api-key": []string{ClientId}, "trakt-api-version": []string{ApiVersion}, "User-Agent": []string{clearance.UserAgent}, "Cookie": []string{clearance.Cookies}, } req := napping.Request{ Url: fmt.Sprintf("%s/%s", ApiUrl, endPoint), Method: "GET", Params: ¶ms, Header: &header, } resp, err = napping.Send(&req) if err == nil && resp.Status() == 403 && retries < 3 { retries += 1 err = newClearance() if err == nil { resp, err = GetWithAuth(endPoint, params) } } return resp, err }
func GetShow(showId int, language string) *Show { var show *Show cacheStore := cache.NewFileStore(path.Join(config.Get().ProfilePath, "cache")) key := fmt.Sprintf("com.tmdb.show.%d.%s", showId, language) if err := cacheStore.Get(key, &show); err != nil { rateLimiter.Call(func() { urlValues := napping.Params{ "api_key": apiKey, "append_to_response": "credits,images,alternative_titles,translations,external_ids", "language": language, }.AsUrlValues() napping.Get( tmdbEndpoint+"tv/"+strconv.Itoa(showId), &urlValues, &show, nil, ) }) if show != nil { cacheStore.Set(key, show, cacheTime) } } if show == nil { return nil } switch t := show.RawPopularity.(type) { case string: if popularity, err := strconv.ParseFloat(t, 64); err == nil { show.Popularity = popularity } case float64: show.Popularity = t } return show }
func TVIndex(ctx *gin.Context) { items := xbmc.ListItems{ {Label: "LOCALIZE[30209]", Path: UrlForXBMC("/shows/search"), Thumbnail: config.AddonResource("img", "search.png")}, {Label: "LOCALIZE[30056]", Path: UrlForXBMC("/shows/trakt/"), Thumbnail: config.AddonResource("img", "trakt.png")}, {Label: "LOCALIZE[30238]", Path: UrlForXBMC("/shows/recent/episodes"), Thumbnail: config.AddonResource("img", "fresh.png")}, {Label: "LOCALIZE[30237]", Path: UrlForXBMC("/shows/recent/shows"), Thumbnail: config.AddonResource("img", "clock.png")}, {Label: "LOCALIZE[30210]", Path: UrlForXBMC("/shows/popular"), Thumbnail: config.AddonResource("img", "popular.png")}, {Label: "LOCALIZE[30211]", Path: UrlForXBMC("/shows/top"), Thumbnail: config.AddonResource("img", "top_rated.png")}, {Label: "LOCALIZE[30212]", Path: UrlForXBMC("/shows/mostvoted"), Thumbnail: config.AddonResource("img", "most_voted.png")}, } for _, genre := range tmdb.GetTVGenres(config.Get().Language) { slug, _ := genreSlugs[genre.Id] items = append(items, &xbmc.ListItem{ Label: genre.Name, Path: UrlForXBMC("/shows/popular/%s", strconv.Itoa(genre.Id)), Thumbnail: config.AddonResource("img", fmt.Sprintf("genre_%s.png", slug)), ContextMenu: [][]string{ []string{"LOCALIZE[30237]", fmt.Sprintf("Container.Update(%s)", UrlForXBMC("/shows/recent/shows/%s", strconv.Itoa(genre.Id)))}, []string{"LOCALIZE[30238]", fmt.Sprintf("Container.Update(%s)", UrlForXBMC("/shows/recent/episodes/%s", strconv.Itoa(genre.Id)))}, }, }) } ctx.JSON(200, xbmc.NewView("", items)) }
func Find(externalId string, externalSource string) *FindResult { var result *FindResult cacheStore := cache.NewFileStore(path.Join(config.Get().ProfilePath, "cache")) key := fmt.Sprintf("com.tmdb.find.%s.%s", externalSource, externalId) if err := cacheStore.Get(key, &result); err != nil { rateLimiter.Call(func() { urlValues := napping.Params{ "api_key": apiKey, "external_source": externalSource, }.AsUrlValues() resp, err := napping.Get( tmdbEndpoint+"find/"+externalId, &urlValues, &result, nil, ) if err != nil { panic(err) } if resp.Status() != 200 { panic(errors.New(fmt.Sprintf("Bad status: %d", resp.Status()))) } cacheStore.Set(key, result, 365*24*time.Hour) }) } return result }
func GetSeason(showId int, seasonNumber int, language string) *Season { var season *Season cacheStore := cache.NewFileStore(path.Join(config.Get().ProfilePath, "cache")) key := fmt.Sprintf("com.tmdb.season.%d.%d.%s", showId, seasonNumber, language) if err := cacheStore.Get(key, &season); err != nil { rateLimiter.Call(func() { urlValues := napping.Params{ "api_key": apiKey, "append_to_response": "credits,images,videos,external_ids", "language": language, }.AsUrlValues() resp, err := napping.Get( fmt.Sprintf("%stv/%d/season/%d", tmdbEndpoint, showId, seasonNumber), &urlValues, &season, nil, ) if err != nil { panic(err) } if resp.Status() != 200 { panic(errors.New(fmt.Sprintf("Bad status: %d", resp.Status()))) } }) season.EpisodeCount = len(season.Episodes) if season != nil { cacheStore.Set(key, season, cacheTime) } } if season == nil { return nil } return season }
func InJsonDB(ID string, ltype int) (bool, error) { var db DataBase LibraryPath := config.Get().LibraryPath DBPath := filepath.Join(LibraryPath, fmt.Sprintf("%s.json", DBName)) if _, err := os.Stat(DBPath); err == nil { file, err := ioutil.ReadFile(DBPath) if err != nil { return false, err } json.Unmarshal(file, &db) } if ltype == LMovie { for _, movieId := range db.Movies { if movieId == ID { return true, nil } } } else if ltype == LShow { for _, showId := range db.Shows { if showId == ID { return true, nil } } } else { return false, fmt.Errorf("Unknown content type") } return false, nil }
func GetList(listId string, language string, page int) Movies { var results *List listResultsPerPage := config.Get().ResultsPerPage rateLimiter.Call(func() { urlValues := napping.Params{ "api_key": apiKey, }.AsUrlValues() resp, err := napping.Get( tmdbEndpoint+"list/"+listId, &urlValues, &results, nil, ) if err != nil { log.Error(err.Error()) xbmc.Notify("Quasar", "GetList failed, check your logs.", config.AddonIcon()) } else if resp.Status() != 200 { message := fmt.Sprintf("GetList bad status: %d", resp.Status()) log.Error(message) xbmc.Notify("Quasar", message, config.AddonIcon()) } }) tmdbIds := make([]int, 0, listResultsPerPage) for i, movie := range results.Items { if i < page*listResultsPerPage { continue } tmdbIds = append(tmdbIds, movie.Id) if i >= (startPage+page)*listResultsPerPage-1 { break } } return GetMovies(tmdbIds, language) }
func TopMovies(topCategory string, page string) (movies []*Movies, err error) { endPoint := "movies/" + topCategory params := napping.Params{ "page": page, "limit": strconv.Itoa(config.Get().ResultsPerPage), "extended": "full,images", }.AsUrlValues() resp, err := Get(endPoint, params) if err != nil { return movies, err } else if resp.Status() != 200 { return movies, errors.New(fmt.Sprintf("TopMovies bad status: %d", resp.Status())) } if topCategory == "popular" { var movieList []*Movie resp.Unmarshal(&movieList) movieListing := make([]*Movies, 0) for _, movie := range movieList { movieItem := Movies{ Movie: movie, } movieListing = append(movieListing, &movieItem) } movies = movieListing } else { resp.Unmarshal(&movies) } return movies, err }
func RemoveShow(ctx *gin.Context) { LibraryPath := config.Get().LibraryPath ShowsLibraryPath := filepath.Join(LibraryPath, "Shows") DBPath := filepath.Join(LibraryPath, fmt.Sprintf("%s.json", DBName)) showId := ctx.Params.ByName("showId") Id, _ := strconv.Atoi(showId) show := tmdb.GetShow(Id, "en") if show == nil { ctx.String(404, "") return } ShowStrm := toFileName(fmt.Sprintf("%s (%s)", show.Name, strings.Split(show.FirstAirDate, "-")[0])) ShowPath := filepath.Join(ShowsLibraryPath, ShowStrm) if err := RemoveFromJsonDB(DBPath, showId, LShow); err != nil { libraryLog.Error("Unable to remove show from db") ctx.String(404, "") return } if err := os.RemoveAll(ShowPath); err != nil { libraryLog.Error("Unable to remove show folder") ctx.String(404, "") return } xbmc.Notify("Quasar", "LOCALIZE[30222]", config.AddonIcon()) ctx.String(200, "") xbmc.VideoLibraryClean() ClearCache(ctx) xbmc.Refresh() libraryLog.Notice("Show removed") }
func Lookup(ctx *gin.Context) { var db DataBase LibraryPath := config.Get().LibraryPath DBPath := filepath.Join(LibraryPath, fmt.Sprintf("%s.json", DBName)) if _, err := os.Stat(DBPath); err == nil { file, err := ioutil.ReadFile(DBPath) if err != nil { ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") ctx.JSON(200, gin.H{ "success": false, }) return } json.Unmarshal(file, &db) } Movies := make([]*Item, 0, len(db.Movies)) Shows := make([]*Item, 0, len(db.Shows)) for i := 0; i < len(db.Movies); i++ { movie := tmdb.GetMovieById(db.Movies[i], "en") Movies = append(Movies, &Item{ Id: db.Movies[i], Title: movie.OriginalTitle, Year: strings.Split(movie.ReleaseDate, "-")[0], Overview: movie.Overview, Poster: tmdb.ImageURL(movie.PosterPath, "w500"), }) } for i := 0; i < len(db.Shows); i++ { showId, _ := strconv.Atoi(db.Shows[i]) show := tmdb.GetShow(showId, "en") if show == nil { ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") ctx.JSON(200, gin.H{ "success": false, }) return } Shows = append(Shows, &Item{ Id: db.Shows[i], Title: show.Name, Year: strings.Split(show.FirstAirDate, "-")[0], Overview: show.Overview, Poster: tmdb.ImageURL(show.PosterPath, "w500"), }) } ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") ctx.JSON(200, gin.H{ "success": true, "results": gin.H{ "movies": Movies, "shows": Shows, }, }) }
func RemoveMovie(ctx *gin.Context) { LibraryPath := config.Get().LibraryPath MoviesLibraryPath := filepath.Join(LibraryPath, "Movies") DBPath := filepath.Join(LibraryPath, fmt.Sprintf("%s.json", DBName)) imdbId := ctx.Params.ByName("imdbId") movie := tmdb.GetMovieFromIMDB(imdbId, "en") MovieStrm := toFileName(fmt.Sprintf("%s (%s)", movie.OriginalTitle, strings.Split(movie.ReleaseDate, "-")[0])) MoviePath := filepath.Join(MoviesLibraryPath, MovieStrm) if err := RemoveFromJsonDB(DBPath, imdbId, LMovie); err != nil { libraryLog.Info("Unable to remove movie from db") ctx.String(404, "") return } if err := os.RemoveAll(MoviePath); err != nil { libraryLog.Info("Unable to remove movie folder") ctx.String(404, "") return } xbmc.Notify("Quasar", "LOCALIZE[30222]", config.AddonIcon()) ctx.String(200, "") xbmc.VideoLibraryClean() libraryLog.Info("Movie removed") }
func (as *AddonSearcher) call(method string, searchObject interface{}) []*bittorrent.Torrent { torrents := make([]*bittorrent.Torrent, 0) cid, c := GetCallback() cbUrl := fmt.Sprintf("%s/callbacks/%s", util.GetHTTPHost(), cid) payload := &SearchPayload{ Method: method, CallbackURL: cbUrl, SearchObject: searchObject, } xbmc.ExecuteAddon(as.addonId, payload.String()) timeout := providerTimeout() conf := config.Get() if conf.CustomProviderTimeoutEnabled == true { timeout = time.Duration(conf.CustomProviderTimeout) * time.Second } select { case <-time.After(timeout): as.log.Info("Provider %s was too slow. Ignored.", as.addonId) RemoveCallback(cid) case result := <-c: json.Unmarshal(result, &torrents) } return torrents }
func Find(externalId string, externalSource string) *FindResult { var result *FindResult cacheStore := cache.NewFileStore(path.Join(config.Get().ProfilePath, "cache")) key := fmt.Sprintf("com.tmdb.find.%s.%s", externalSource, externalId) if err := cacheStore.Get(key, &result); err != nil { rateLimiter.Call(func() { urlValues := napping.Params{ "api_key": apiKey, "external_source": externalSource, }.AsUrlValues() resp, err := napping.Get( tmdbEndpoint+"find/"+externalId, &urlValues, &result, nil, ) if err != nil { log.Error(err.Error()) xbmc.Notify("Quasar", "Failed Find call, check your logs.", config.AddonIcon()) } else if resp.Status() != 200 { message := fmt.Sprintf("Find call bad status: %d", resp.Status()) log.Error(message) xbmc.Notify("Quasar", message, config.AddonIcon()) } cacheStore.Set(key, result, 365*24*time.Hour) }) } return result }
func TopRatedMovies(ctx *gin.Context) { genre := ctx.Params.ByName("genre") if genre == "0" { genre = "" } page, _ := strconv.Atoi(ctx.DefaultQuery("page", "0")) renderMovies(tmdb.TopRatedMovies(genre, config.Get().Language, page), ctx, page, "") }