mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 13:23:20 -07:00
Added the ability to show seasonal content ;)
This commit is contained in:
parent
b406dc0118
commit
bc59a5051e
11 changed files with 97 additions and 3 deletions
|
@ -28,5 +28,6 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
Task<MovieFullInfoViewModel> GetMovieInfoByImdbId(string imdbId, CancellationToken requestAborted);
|
Task<MovieFullInfoViewModel> GetMovieInfoByImdbId(string imdbId, CancellationToken requestAborted);
|
||||||
Task<IEnumerable<StreamingData>> GetStreamInformation(int movieDbId, CancellationToken cancellationToken);
|
Task<IEnumerable<StreamingData>> GetStreamInformation(int movieDbId, CancellationToken cancellationToken);
|
||||||
Task<IEnumerable<SearchMovieViewModel>> RecentlyRequestedMovies(int currentlyLoaded, int toLoad, CancellationToken cancellationToken);
|
Task<IEnumerable<SearchMovieViewModel>> RecentlyRequestedMovies(int currentlyLoaded, int toLoad, CancellationToken cancellationToken);
|
||||||
|
Task<IEnumerable<SearchMovieViewModel>> SeasonalList(int currentPosition, int amountToLoad, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,6 +19,7 @@ using Ombi.Store.Repository;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -29,7 +30,7 @@ namespace Ombi.Core.Engine.V2
|
||||||
{
|
{
|
||||||
public MovieSearchEngineV2(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
|
public MovieSearchEngineV2(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
|
||||||
ILogger<MovieSearchEngineV2> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub,
|
ILogger<MovieSearchEngineV2> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub,
|
||||||
ISettingsService<CustomizationSettings> customizationSettings, IMovieRequestEngine movieRequestEngine)
|
ISettingsService<CustomizationSettings> customizationSettings, IMovieRequestEngine movieRequestEngine, IHttpClientFactory httpClientFactory)
|
||||||
: base(identity, service, r, um, mem, s, sub)
|
: base(identity, service, r, um, mem, s, sub)
|
||||||
{
|
{
|
||||||
MovieApi = movApi;
|
MovieApi = movApi;
|
||||||
|
@ -37,6 +38,7 @@ namespace Ombi.Core.Engine.V2
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
_customizationSettings = customizationSettings;
|
_customizationSettings = customizationSettings;
|
||||||
_movieRequestEngine = movieRequestEngine;
|
_movieRequestEngine = movieRequestEngine;
|
||||||
|
_client = httpClientFactory.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMovieDbApi MovieApi { get; }
|
private IMovieDbApi MovieApi { get; }
|
||||||
|
@ -44,6 +46,7 @@ namespace Ombi.Core.Engine.V2
|
||||||
private ILogger Logger { get; }
|
private ILogger Logger { get; }
|
||||||
private readonly ISettingsService<CustomizationSettings> _customizationSettings;
|
private readonly ISettingsService<CustomizationSettings> _customizationSettings;
|
||||||
private readonly IMovieRequestEngine _movieRequestEngine;
|
private readonly IMovieRequestEngine _movieRequestEngine;
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
|
||||||
public async Task<MovieFullInfoViewModel> GetFullMovieInformation(int theMovieDbId, CancellationToken cancellationToken, string langCode = null)
|
public async Task<MovieFullInfoViewModel> GetFullMovieInformation(int theMovieDbId, CancellationToken cancellationToken, string langCode = null)
|
||||||
{
|
{
|
||||||
|
@ -190,6 +193,30 @@ namespace Ombi.Core.Engine.V2
|
||||||
return await TransformMovieResultsToResponse(results);
|
return await TransformMovieResultsToResponse(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchMovieViewModel>> SeasonalList(int currentPosition, int amountToLoad, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var langCode = await DefaultLanguageCode(null);
|
||||||
|
|
||||||
|
var result = await _client.GetAsync("https://raw.githubusercontent.com/Ombi-app/Ombi.News/main/Seasonal.md");
|
||||||
|
var keyWordIds = await result.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(keyWordIds))
|
||||||
|
{
|
||||||
|
return new List<SearchMovieViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
|
||||||
|
|
||||||
|
var results = new List<MovieDbSearchResult>();
|
||||||
|
foreach (var pagesToLoad in pages)
|
||||||
|
{
|
||||||
|
var apiResult = await Cache.GetOrAdd(nameof(SeasonalList) + pagesToLoad.Page + langCode + keyWordIds,
|
||||||
|
async () => await MovieApi.GetMoviesViaKeywords(keyWordIds, langCode, cancellationToken, pagesToLoad.Page), DateTime.Now.AddHours(12));
|
||||||
|
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
|
||||||
|
}
|
||||||
|
return await TransformMovieResultsToResponse(results);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets recently requested movies
|
/// Gets recently requested movies
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace Ombi.Api.TheMovieDb
|
||||||
Task<List<MovieDbSearchResult>> PopularMovies(string languageCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken));
|
Task<List<MovieDbSearchResult>> PopularMovies(string languageCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken));
|
||||||
Task<List<MovieDbSearchResult>> PopularTv(string langCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken));
|
Task<List<MovieDbSearchResult>> PopularTv(string langCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken));
|
||||||
Task<List<MovieDbSearchResult>> SearchMovie(string searchTerm, int? year, string languageCode);
|
Task<List<MovieDbSearchResult>> SearchMovie(string searchTerm, int? year, string languageCode);
|
||||||
|
Task<List<MovieDbSearchResult>> GetMoviesViaKeywords(string keywordId, string langCode, CancellationToken cancellationToken, int? page = null);
|
||||||
Task<List<TvSearchResult>> SearchTv(string searchTerm, string year = default);
|
Task<List<TvSearchResult>> SearchTv(string searchTerm, string year = default);
|
||||||
Task<List<MovieDbSearchResult>> TopRated(string languageCode, int? page = null);
|
Task<List<MovieDbSearchResult>> TopRated(string languageCode, int? page = null);
|
||||||
Task<List<MovieDbSearchResult>> Upcoming(string languageCode, int? page = null);
|
Task<List<MovieDbSearchResult>> Upcoming(string languageCode, int? page = null);
|
||||||
|
|
|
@ -331,6 +331,32 @@ namespace Ombi.Api.TheMovieDb
|
||||||
return await Api.Request<SeasonDetails>(request, token);
|
return await Api.Request<SeasonDetails>(request, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<MovieDbSearchResult>> GetMoviesViaKeywords(string keywordId, string langCode, CancellationToken cancellationToken, int? page = null)
|
||||||
|
{
|
||||||
|
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
|
||||||
|
request.AddQueryString("api_key", ApiToken);
|
||||||
|
request.AddQueryString("language", langCode);
|
||||||
|
request.AddQueryString("sort_by", "vote_average.desc");
|
||||||
|
|
||||||
|
request.AddQueryString("with_keywords", keywordId);
|
||||||
|
|
||||||
|
// `vote_count` consideration isn't explicitly documented, but using only the `sort_by` filter
|
||||||
|
// does not provide the same results as `/movie/top_rated`. This appears to be adequate enough
|
||||||
|
// to filter out extremely high-rated movies due to very little votes
|
||||||
|
request.AddQueryString("vote_count.gte", "250");
|
||||||
|
|
||||||
|
if (page != null)
|
||||||
|
{
|
||||||
|
request.AddQueryString("page", page.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
await AddDiscoverSettings(request);
|
||||||
|
await AddGenreFilter(request, "movie");
|
||||||
|
AddRetry(request);
|
||||||
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request, cancellationToken);
|
||||||
|
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<Keyword>> SearchKeyword(string searchTerm)
|
public async Task<List<Keyword>> SearchKeyword(string searchTerm)
|
||||||
{
|
{
|
||||||
var request = new Request("search/keyword", BaseUri, HttpMethod.Get);
|
var request = new Request("search/keyword", BaseUri, HttpMethod.Get);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="right">
|
<div class="right" *ngIf="discoverType !== DiscoverType.Seasonal">
|
||||||
<mat-button-toggle-group name="discoverMode" (change)="toggleChanged($event)" value="{{discoverOptions}}" class="discover-filter-buttons-group">
|
<mat-button-toggle-group name="discoverMode" (change)="toggleChanged($event)" value="{{discoverOptions}}" class="discover-filter-buttons-group">
|
||||||
<mat-button-toggle id="{{id}}Combined" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Combined}" value="{{DiscoverOption.Combined}}" class="discover-filter-button">{{'Discovery.Combined' | translate}}</mat-button-toggle>
|
<mat-button-toggle id="{{id}}Combined" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Combined}" value="{{DiscoverOption.Combined}}" class="discover-filter-button">{{'Discovery.Combined' | translate}}</mat-button-toggle>
|
||||||
<mat-button-toggle id="{{id}}Movie" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Movie}" value="{{DiscoverOption.Movie}}" class="discover-filter-button">{{'Discovery.Movies' | translate}}</mat-button-toggle>
|
<mat-button-toggle id="{{id}}Movie" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Movie}" value="{{DiscoverOption.Movie}}" class="discover-filter-button">{{'Discovery.Movies' | translate}}</mat-button-toggle>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, OnInit, Input, ViewChild } from "@angular/core";
|
import { Component, OnInit, Input, ViewChild, Output, EventEmitter } from "@angular/core";
|
||||||
import { DiscoverOption, IDiscoverCardResult } from "../../interfaces";
|
import { DiscoverOption, IDiscoverCardResult } from "../../interfaces";
|
||||||
import { ISearchMovieResult, ISearchTvResult, RequestType } from "../../../interfaces";
|
import { ISearchMovieResult, ISearchTvResult, RequestType } from "../../../interfaces";
|
||||||
import { SearchV2Service } from "../../../services";
|
import { SearchV2Service } from "../../../services";
|
||||||
|
@ -11,6 +11,7 @@ export enum DiscoverType {
|
||||||
Trending,
|
Trending,
|
||||||
Popular,
|
Popular,
|
||||||
RecentlyRequested,
|
RecentlyRequested,
|
||||||
|
Seasonal,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -23,6 +24,7 @@ export class CarouselListComponent implements OnInit {
|
||||||
@Input() public discoverType: DiscoverType;
|
@Input() public discoverType: DiscoverType;
|
||||||
@Input() public id: string;
|
@Input() public id: string;
|
||||||
@Input() public isAdmin: boolean;
|
@Input() public isAdmin: boolean;
|
||||||
|
@Output() public movieCount: EventEmitter<number> = new EventEmitter();
|
||||||
@ViewChild('carousel', {static: false}) carousel: Carousel;
|
@ViewChild('carousel', {static: false}) carousel: Carousel;
|
||||||
|
|
||||||
public DiscoverOption = DiscoverOption;
|
public DiscoverOption = DiscoverOption;
|
||||||
|
@ -33,6 +35,7 @@ export class CarouselListComponent implements OnInit {
|
||||||
public responsiveOptions: any;
|
public responsiveOptions: any;
|
||||||
public RequestType = RequestType;
|
public RequestType = RequestType;
|
||||||
public loadingFlag: boolean;
|
public loadingFlag: boolean;
|
||||||
|
public DiscoverType = DiscoverType;
|
||||||
|
|
||||||
get mediaTypeStorageKey() {
|
get mediaTypeStorageKey() {
|
||||||
return "DiscoverOptions" + this.discoverType.toString();
|
return "DiscoverOptions" + this.discoverType.toString();
|
||||||
|
@ -220,7 +223,10 @@ export class CarouselListComponent implements OnInit {
|
||||||
break
|
break
|
||||||
case DiscoverType.RecentlyRequested:
|
case DiscoverType.RecentlyRequested:
|
||||||
this.movies = await this.searchService.recentlyRequestedMoviesByPage(this.currentlyLoaded, this.amountToLoad);
|
this.movies = await this.searchService.recentlyRequestedMoviesByPage(this.currentlyLoaded, this.amountToLoad);
|
||||||
|
case DiscoverType.Seasonal:
|
||||||
|
this.movies = await this.searchService.seasonalMoviesByPage(this.currentlyLoaded, this.amountToLoad);
|
||||||
}
|
}
|
||||||
|
this.movieCount.emit(this.movies.length);
|
||||||
this.currentlyLoaded += this.amountToLoad;
|
this.currentlyLoaded += this.amountToLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
<div class="small-middle-container">
|
<div class="small-middle-container">
|
||||||
|
|
||||||
|
<div class="section" [hidden]="!showSeasonal">
|
||||||
|
<h2>{{'Discovery.SeasonalTab' | translate}}</h2>
|
||||||
|
<div>
|
||||||
|
<carousel-list [id]="'seasonal'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Seasonal" (movieCount)="setSeasonalMovieCount($event)"></carousel-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>{{'Discovery.PopularTab' | translate}}</h2>
|
<h2>{{'Discovery.PopularTab' | translate}}</h2>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { AuthService } from "../../../auth/auth.service";
|
import { AuthService } from "../../../auth/auth.service";
|
||||||
import { DiscoverType } from "../carousel-list/carousel-list.component";
|
import { DiscoverType } from "../carousel-list/carousel-list.component";
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ export class DiscoverComponent implements OnInit {
|
||||||
|
|
||||||
public DiscoverType = DiscoverType;
|
public DiscoverType = DiscoverType;
|
||||||
public isAdmin: boolean;
|
public isAdmin: boolean;
|
||||||
|
public showSeasonal: boolean;
|
||||||
|
|
||||||
constructor(private authService: AuthService) { }
|
constructor(private authService: AuthService) { }
|
||||||
|
|
||||||
|
@ -18,4 +20,9 @@ export class DiscoverComponent implements OnInit {
|
||||||
this.isAdmin = this.authService.isAdmin();
|
this.isAdmin = this.authService.isAdmin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setSeasonalMovieCount(count: number) {
|
||||||
|
if (count > 0) {
|
||||||
|
this.showSeasonal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,10 @@ export class SearchV2Service extends ServiceHelpers {
|
||||||
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/requested/${currentlyLoaded}/${toLoad}`).toPromise();
|
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/requested/${currentlyLoaded}/${toLoad}`).toPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public seasonalMoviesByPage(currentlyLoaded: number, toLoad: number): Promise<ISearchMovieResult[]> {
|
||||||
|
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/seasonal/${currentlyLoaded}/${toLoad}`).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
public nowPlayingMovies(): Observable<ISearchMovieResult[]> {
|
public nowPlayingMovies(): Observable<ISearchMovieResult[]> {
|
||||||
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/nowplaying`);
|
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/nowplaying`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,19 @@ namespace Ombi.Controllers.V2
|
||||||
return await _movieEngineV2.PopularMovies(currentPosition, amountToLoad, Request.HttpContext.RequestAborted);
|
return await _movieEngineV2.PopularMovies(currentPosition, amountToLoad, Request.HttpContext.RequestAborted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns Seasonal Movies
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>We use TheMovieDb as the Movie Provider</remarks>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("movie/seasonal/{currentPosition}/{amountToLoad}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesDefaultResponseType]
|
||||||
|
public async Task<IEnumerable<SearchMovieViewModel>> Seasonal(int currentPosition, int amountToLoad)
|
||||||
|
{
|
||||||
|
return await _movieEngineV2.SeasonalList(currentPosition, amountToLoad, Request.HttpContext.RequestAborted);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns Recently Requested Movies using Paging
|
/// Returns Recently Requested Movies using Paging
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -295,6 +295,7 @@
|
||||||
"PopularTab": "Popular",
|
"PopularTab": "Popular",
|
||||||
"TrendingTab": "Trending",
|
"TrendingTab": "Trending",
|
||||||
"UpcomingTab": "Upcoming",
|
"UpcomingTab": "Upcoming",
|
||||||
|
"SeasonalTab": "Seasonal",
|
||||||
"RecentlyRequestedTab": "Recently Requested",
|
"RecentlyRequestedTab": "Recently Requested",
|
||||||
"Movies": "Movies",
|
"Movies": "Movies",
|
||||||
"Combined": "Combined",
|
"Combined": "Combined",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue