Merge branch 'feature/tvmaze-replacement' into feature/automation

This commit is contained in:
Jamie 2021-03-09 21:52:45 +00:00 committed by GitHub
commit 58b8453ae7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 3660 additions and 355 deletions

View file

@ -5,19 +5,19 @@ steps:
packageType: 'sdk'
version: '5.x'
- task: DotNetCoreInstaller@1
displayName: 'Use .NET Core sdk for versioning'
inputs:
packageType: 'sdk'
version: '3.1.x'
# - task: DotNetCoreInstaller@1
# displayName: 'Use .NET Core sdk for versioning'
# inputs:
# packageType: 'sdk'
# version: '3.1.x'
- task: PowerShell@2
displayName: 'Set Version'
inputs:
targetType: 'inline'
script: |
dotnet tool install -g dotnet-setversion
setversion -r $(BuildVersion)
# - task: PowerShell@2
# displayName: 'Set Version'
# inputs:
# targetType: 'inline'
# script: |
# dotnet tool install -g dotnet-setversion
# setversion -r $(BuildVersion)
- task: DotNetCoreCLI@2
displayName: 'publish $(runtime)'

Binary file not shown.

View file

@ -86,12 +86,16 @@ namespace Ombi.Api
_handler = await GetHandler();
}
_client = new HttpClient(_handler);
_client.DefaultRequestHeaders.Add("User-Agent",$"Ombi/{_runtimeVersion} (https://ombi.io/)");
_client.DefaultRequestHeaders.Add("User-Agent", $"Ombi/{_runtimeVersion} (https://ombi.io/)");
}
}
private async Task<HttpMessageHandler> GetHandler()
{
if (_cache == null)
{
return new HttpClientHandler();
}
var settings = await _cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await _settings.GetSettingsAsync(), DateTime.Now.AddHours(1));
if (settings.IgnoreCertificateErrors)
{

View file

@ -69,8 +69,8 @@ namespace Ombi.Core.Engine
{
var allResults = await TvRepository.Get().ToListAsync();
var distinctResults = allResults.DistinctBy(x => x.TvDbId);
_dbTv = distinctResults.ToDictionary(x => x.TvDbId);
var distinctResults = allResults.DistinctBy(x => x.ExternalProviderId);
_dbTv = distinctResults.ToDictionary(x => x.ExternalProviderId);
_cacheTime = now;
}
return _dbTv;

View file

@ -28,7 +28,7 @@ namespace Ombi.Core.Engine.Demo
ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache,
ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub, IOptions<DemoLists> lists, IImageService imageService,
ISettingsService<CustomizationSettings> custom)
: base(identity, service, tvMaze, mapper, trakt, r, um, custom, memCache, s, sub, imageService)
: base(identity, service, tvMaze, mapper, trakt, r, um, custom, memCache, s, sub, imageService, null)
{
_demoLists = lists.Value;
}

View file

@ -13,6 +13,7 @@ namespace Ombi.Core.Engine.Interfaces
Task RemoveTvRequest(int requestId);
Task<TvRequests> GetTvRequest(int requestId);
Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
Task<RequestEngineResult> RequestTvShow(TvRequestViewModelV2 tv);
Task<RequestEngineResult> DenyChildRequest(int requestId, string reason);
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);

View file

@ -1,5 +1,6 @@
using Ombi.Core.Models.Search;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Ombi.Core.Engine.Interfaces
@ -7,7 +8,7 @@ namespace Ombi.Core.Engine.Interfaces
public interface ITvSearchEngine
{
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<SearchTvShowViewModel> GetShowInformation(string movieDbId, CancellationToken token);
Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad, bool includeImages = false);
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();

View file

@ -7,8 +7,8 @@ namespace Ombi.Core
{
public interface ITVSearchEngineV2
{
Task<SearchFullInfoTvShowViewModel> GetShowInformation(int tvdbid);
Task<SearchFullInfoTvShowViewModel> GetShowByRequest(int requestId);
Task<IEnumerable<StreamingData>> GetStreamInformation(int tvDbId, int tvMazeId, CancellationToken cancellationToken);
Task<SearchFullInfoTvShowViewModel> GetShowInformation(string tvdbid, CancellationToken token);
Task<SearchFullInfoTvShowViewModel> GetShowByRequest(int requestId, CancellationToken token);
Task<IEnumerable<StreamingData>> GetStreamInformation(int movieDbId, CancellationToken cancellationToken);
}
}

View file

@ -189,7 +189,7 @@ namespace Ombi.Core.Engine
}
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
IEnumerable<MovieSearchResult> movies)
IEnumerable<MovieDbSearchResult> movies)
{
var viewMovies = new List<SearchMovieViewModel>();
foreach (var movie in movies)
@ -244,7 +244,7 @@ namespace Ombi.Core.Engine
}
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieSearchResult movie)
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieDbSearchResult movie)
{
var viewMovie = Mapper.Map<SearchMovieViewModel>(movie);
return await ProcessSingleMovie(viewMovie);

View file

@ -148,6 +148,106 @@ namespace Ombi.Core.Engine
return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf);
}
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModelV2 tv)
{
var user = await GetUser();
var canRequestOnBehalf = false;
if (tv.RequestOnBehalf.HasValue())
{
canRequestOnBehalf = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);
if (!canRequestOnBehalf)
{
return new RequestEngineResult
{
Result = false,
Message = "You do not have the correct permissions to request on behalf of users!",
ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
};
}
}
var tvBuilder = new TvShowRequestBuilderV2(MovieDbApi);
(await tvBuilder
.GetShowInfo(tv.TheMovieDbId))
.CreateTvList(tv)
.CreateChild(tv, canRequestOnBehalf ? tv.RequestOnBehalf : user.Id);
await tvBuilder.BuildEpisodes(tv);
var ruleResults = await RunRequestRules(tvBuilder.ChildRequest);
var results = ruleResults as RuleResult[] ?? ruleResults.ToArray();
if (results.Any(x => !x.Success))
{
return new RequestEngineResult
{
ErrorMessage = results.FirstOrDefault(x => !string.IsNullOrEmpty(x.Message)).Message
};
}
// Check if we have auto approved the request, if we have then mark the episodes as approved
if (tvBuilder.ChildRequest.Approved)
{
foreach (var seasons in tvBuilder.ChildRequest.SeasonRequests)
{
foreach (var ep in seasons.Episodes)
{
ep.Approved = true;
ep.Requested = true;
}
}
}
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.ExternalProviderId == tv.TheMovieDbId);
if (existingRequest != null)
{
// Remove requests we already have, we just want new ones
foreach (var existingSeason in existingRequest.ChildRequests)
foreach (var existing in existingSeason.SeasonRequests)
{
var newChild = tvBuilder.ChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == existing.SeasonNumber);
if (newChild != null)
{
// We have some requests in this season...
// Let's find the episodes.
foreach (var existingEp in existing.Episodes)
{
var duplicateEpisode = newChild.Episodes.FirstOrDefault(x => x.EpisodeNumber == existingEp.EpisodeNumber);
if (duplicateEpisode != null)
{
// Remove it.
newChild.Episodes.Remove(duplicateEpisode);
}
}
if (!newChild.Episodes.Any())
{
// We may have removed all episodes
tvBuilder.ChildRequest.SeasonRequests.Remove(newChild);
}
}
}
// Remove the ID since this is a new child
// This was a TVDBID for the request rules to run
tvBuilder.ChildRequest.Id = 0;
if (!tvBuilder.ChildRequest.SeasonRequests.Any())
{
// Looks like we have removed them all! They were all duplicates...
return new RequestEngineResult
{
Result = false,
ErrorMessage = "This has already been requested"
};
}
return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf);
}
// This is a new request
var newRequest = tvBuilder.CreateNewRequest(tv);
return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf);
}
public async Task<RequestsViewModel<TvRequests>> GetRequests(int count, int position, OrderFilterModel type)
{
var shouldHide = await HideFromOtherUsers();

View file

@ -6,7 +6,6 @@ using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Store.Repository;
using System;
@ -16,12 +15,13 @@ using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Repository.Requests;
using Microsoft.Extensions.Caching.Memory;
using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using TraktSharp.Entities;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using System.Threading;
namespace Ombi.Core.Engine
{
@ -29,13 +29,16 @@ namespace Ombi.Core.Engine
{
private readonly ISettingsService<CustomizationSettings> _customizationSettings;
private readonly IImageService _imageService;
private readonly IMovieDbApi _theMovieDbApi;
public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper,
ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ISettingsService<CustomizationSettings> customizationSettings,
ICacheService memCache, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub, IImageService imageService)
ICacheService memCache, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub, IImageService imageService,
IMovieDbApi theMovieDbApi)
: base(identity, service, r, um, memCache, s, sub)
{
_imageService = imageService;
_theMovieDbApi = theMovieDbApi;
TvMazeApi = tvMaze;
Mapper = mapper;
TraktApi = trakt;
@ -48,18 +51,18 @@ namespace Ombi.Core.Engine
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm)
{
var searchResult = await TvMazeApi.Search(searchTerm);
var searchResult = await _theMovieDbApi.SearchTv(searchTerm);
if (searchResult != null)
{
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in searchResult)
foreach (var result in searchResult)
{
if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
{
continue;
}
var mappedResult = await ProcessResult(tvMazeSearch, false);
//if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
//{
// continue;
//}
var mappedResult = await ProcessResult(result, false);
if (mappedResult == null)
{
continue;
@ -71,58 +74,64 @@ namespace Ombi.Core.Engine
return null;
}
public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid)
public async Task<SearchTvShowViewModel> GetShowInformation(string theMovieDbId, CancellationToken token)
{
var show = await Cache.GetOrAdd(nameof(GetShowInformation) + tvdbid,
async () => await TvMazeApi.ShowLookupByTheTvDbId(tvdbid), DateTime.Now.AddHours(12));
var show = await Cache.GetOrAdd(nameof(GetShowInformation) + theMovieDbId,
async () => await _theMovieDbApi.GetTVInfo(theMovieDbId), DateTime.Now.AddHours(12));
if (show == null)
{
// We don't have enough information
return null;
}
var episodes = await Cache.GetOrAdd("TvMazeEpisodeLookup" + show.id,
async () => await TvMazeApi.EpisodeLookup(show.id), DateTime.Now.AddHours(12));
if (episodes == null || !episodes.Any())
{
// We don't have enough information
return null;
}
//var episodes = await Cache.GetOrAdd("TvMazeEpisodeLookup" + show.id,
// async () => await TvMazeApi.EpisodeLookup(show.id), DateTime.Now.AddHours(12));
//if (episodes == null || !episodes.Any())
//{
// // We don't have enough information
// return null;
//}
var mapped = Mapper.Map<SearchTvShowViewModel>(show);
foreach (var e in episodes)
foreach(var tvSeason in show.seasons)
{
var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season);
if (season == null)
{
var newSeason = new SeasonRequests
{
SeasonNumber = e.season,
Episodes = new List<EpisodeRequests>()
};
newSeason.Episodes.Add(new EpisodeRequests
{
Url = e.url.ToHttpsUrl(),
Title = e.name,
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
EpisodeNumber = e.number,
var seasonEpisodes = (await _theMovieDbApi.GetSeasonEpisodes(show.id, tvSeason.season_number, token));
});
mapped.SeasonRequests.Add(newSeason);
}
else
foreach (var episode in seasonEpisodes.episodes)
{
// We already have the season, so just add the episode
season.Episodes.Add(new EpisodeRequests
var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == episode.season_number);
if (season == null)
{
Url = e.url.ToHttpsUrl(),
Title = e.name,
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
EpisodeNumber = e.number,
});
var newSeason = new SeasonRequests
{
SeasonNumber = episode.season_number,
Episodes = new List<EpisodeRequests>()
};
newSeason.Episodes.Add(new EpisodeRequests
{
//Url = episode...ToHttpsUrl(),
Title = episode.name,
AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue,
EpisodeNumber = episode.episode_number,
});
mapped.SeasonRequests.Add(newSeason);
}
else
{
// We already have the season, so just add the episode
season.Episodes.Add(new EpisodeRequests
{
//Url = e.url.ToHttpsUrl(),
Title = episode.name,
AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue,
EpisodeNumber = episode.episode_number,
});
}
}
}
return await ProcessResult(mapped, false);
}
@ -135,12 +144,14 @@ namespace Ombi.Core.Engine
public async Task<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad, bool includeImages = false)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktShow>();
var results = new List<MovieDbSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await Cache.GetOrAdd(nameof(Popular) + pagesToLoad.Page,
async () => await TraktApi.GetPopularShows(pagesToLoad.Page, ResultLimit), DateTime.Now.AddHours(12));
var apiResult = await Cache.GetOrAdd(nameof(Popular) + langCode + pagesToLoad.Page,
async () => await _theMovieDbApi.PopularTv(langCode, pagesToLoad.Page), DateTime.Now.AddHours(12));
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
@ -158,12 +169,14 @@ namespace Ombi.Core.Engine
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated(int currentlyLoaded, int amountToLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktShow>();
var results = new List<MovieDbSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await Cache.GetOrAdd(nameof(Anticipated) + pagesToLoad.Page,
async () => await TraktApi.GetAnticipatedShows(pagesToLoad.Page, ResultLimit), DateTime.Now.AddHours(12));
var apiResult = await Cache.GetOrAdd(nameof(Anticipated) + langCode + pagesToLoad.Page,
async () => await _theMovieDbApi.UpcomingTv(langCode, pagesToLoad.Page), DateTime.Now.AddHours(12));
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
@ -180,12 +193,14 @@ namespace Ombi.Core.Engine
public async Task<IEnumerable<SearchTvShowViewModel>> Trending(int currentlyLoaded, int amountToLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktShow>();
var results = new List<MovieDbSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await Cache.GetOrAdd(nameof(Trending) + pagesToLoad.Page,
async () => await TraktApi.GetTrendingShows(pagesToLoad.Page, ResultLimit), DateTime.Now.AddHours(12));
var apiResult = await Cache.GetOrAdd(nameof(Trending) + langCode + pagesToLoad.Page,
async () => await _theMovieDbApi.TopRatedTv(langCode, pagesToLoad.Page), DateTime.Now.AddHours(12));
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
@ -220,14 +235,15 @@ namespace Ombi.Core.Engine
{
return null;
}
item.TheTvDbId = item.Id.ToString();
if (includeImages)
{
if (item.TheTvDbId.HasValue())
{
item.BackdropPath = await _imageService.GetTvBackground(item.TheTvDbId);
}
}
item.TheMovieDbId = item.Id.ToString();
//item.TheTvDbId = item.Id.ToString();
//if (includeImages)
//{
// if (item.TheTvDbId.HasValue())
// {
// item.BackdropPath = await _imageService.GetTvBackground(item.TheTvDbId);
// }
//}
await RunSearchRules(item);

View file

@ -47,7 +47,7 @@ namespace Ombi.Core.Engine.V2
new ExtraParams
{
Overview = e.Season?.ChildRequest?.ParentRequest?.Overview ?? string.Empty,
ProviderId = e.Season?.ChildRequest?.ParentRequest?.TvDbId ?? 0,
ProviderId = e.Season?.ChildRequest?.ParentRequest?.ExternalProviderId ?? 0,
Type = RequestType.TvShow,
ReleaseDate = e.AirDate,
RequestStatus = e.RequestStatus

View file

@ -127,7 +127,7 @@ namespace Ombi.Core.Engine.V2
var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
var results = new List<MovieDbSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await Cache.GetOrAdd(nameof(PopularMovies) + pagesToLoad.Page + langCode,
@ -161,7 +161,7 @@ namespace Ombi.Core.Engine.V2
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
var results = new List<MovieDbSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await Cache.GetOrAdd(nameof(TopRatedMovies) + pagesToLoad.Page + langCode,
@ -177,7 +177,7 @@ namespace Ombi.Core.Engine.V2
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
var results = new List<MovieDbSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await Cache.GetOrAdd(nameof(NowPlayingMovies) + pagesToLoad.Page + langCode,
@ -213,7 +213,7 @@ namespace Ombi.Core.Engine.V2
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
var results = new List<MovieDbSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await Cache.GetOrAdd(nameof(UpcomingMovies) + pagesToLoad.Page + langCode,
@ -270,7 +270,7 @@ namespace Ombi.Core.Engine.V2
}
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
IEnumerable<MovieSearchResult> movies)
IEnumerable<MovieDbSearchResult> movies)
{
var settings = await _customizationSettings.GetSettingsAsync();
var viewMovies = new List<SearchMovieViewModel>();
@ -286,7 +286,7 @@ namespace Ombi.Core.Engine.V2
return viewMovies;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieSearchResult movie)
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieDbSearchResult movie)
{
var viewMovie = Mapper.Map<SearchMovieViewModel>(movie);
return await ProcessSingleMovie(viewMovie);

View file

@ -43,89 +43,70 @@ namespace Ombi.Core.Engine.V2
}
public async Task<SearchFullInfoTvShowViewModel> GetShowByRequest(int requestId)
public async Task<SearchFullInfoTvShowViewModel> GetShowByRequest(int requestId, CancellationToken token)
{
var request = await RequestService.TvRequestService.Get().FirstOrDefaultAsync(x => x.Id == requestId);
return await GetShowInformation(request.TvDbId);
return await GetShowInformation(request.ExternalProviderId.ToString(), token); // TODO
}
public async Task<SearchFullInfoTvShowViewModel> GetShowInformation(int tvdbid)
public async Task<SearchFullInfoTvShowViewModel> GetShowInformation(string tvdbid, CancellationToken token)
{
var tvdbshow = await Cache.GetOrAdd(nameof(GetShowInformation) + tvdbid,
async () => await _tvMaze.ShowLookupByTheTvDbId(tvdbid), DateTime.Now.AddHours(12));
if (tvdbshow == null)
{
return null;
}
var show = await Cache.GetOrAdd("GetTvFullInformation" + tvdbshow.id,
async () => await _tvMaze.GetTvFullInformation(tvdbshow.id), DateTime.Now.AddHours(12));
var show = await Cache.GetOrAdd(nameof(GetShowInformation) + tvdbid,
async () => await _movieApi.GetTVInfo(tvdbid), DateTime.Now.AddHours(12));
if (show == null)
{
// We don't have enough information
return null;
}
// Setup the task so we can get the data later on if we have a IMDBID
Task<TraktShow> traktInfoTask = null;
if (show.externals?.imdb.HasValue() ?? false)
{
traktInfoTask = Cache.GetOrAdd("GetExtendedTvInfoTrakt" + show.externals?.imdb,
() => _traktApi.GetTvExtendedInfo(show.externals?.imdb), DateTime.Now.AddHours(12));
}
var mapped = _mapper.Map<SearchFullInfoTvShowViewModel>(show);
foreach (var e in show._embedded?.episodes ?? new Api.TvMaze.Models.V2.Episode[0])
{
var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season);
if (season == null)
{
var newSeason = new SeasonRequests
{
SeasonNumber = e.season,
Episodes = new List<EpisodeRequests>()
};
newSeason.Episodes.Add(new EpisodeRequests
{
Url = e.url.ToHttpsUrl(),
Title = e.name,
AirDate = e.airstamp,
EpisodeNumber = e.number,
});
mapped.SeasonRequests.Add(newSeason);
}
else
foreach (var tvSeason in show.seasons.Where(x => x.season_number != 0)) // skip the first season
{
var seasonEpisodes = (await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, token));
foreach (var episode in seasonEpisodes.episodes)
{
// We already have the season, so just add the episode
season.Episodes.Add(new EpisodeRequests
var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == episode.season_number);
if (season == null)
{
Url = e.url.ToHttpsUrl(),
Title = e.name,
AirDate = e.airstamp,
EpisodeNumber = e.number,
});
var newSeason = new SeasonRequests
{
SeasonNumber = episode.season_number,
Overview = tvSeason.overview,
Episodes = new List<EpisodeRequests>()
};
newSeason.Episodes.Add(new EpisodeRequests
{
//Url = episode...ToHttpsUrl(),
Title = episode.name,
AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue,
EpisodeNumber = episode.episode_number,
});
mapped.SeasonRequests.Add(newSeason);
}
else
{
// We already have the season, so just add the episode
season.Episodes.Add(new EpisodeRequests
{
//Url = e.url.ToHttpsUrl(),
Title = episode.name,
AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue,
EpisodeNumber = episode.episode_number,
});
}
}
}
return await ProcessResult(mapped, traktInfoTask);
return await ProcessResult(mapped);
}
public async Task<IEnumerable<StreamingData>> GetStreamInformation(int tvDbId, int tvMazeId, CancellationToken cancellationToken)
public async Task<IEnumerable<StreamingData>> GetStreamInformation(int movieDbId, CancellationToken cancellationToken)
{
var tvdbshow = await Cache.GetOrAdd(nameof(GetShowInformation) + tvMazeId,
async () => await _tvMaze.ShowLookupByTheTvDbId(tvMazeId), DateTime.Now.AddHours(12));
if (tvdbshow == null)
{
return null;
}
/// this is a best effort guess since TV maze do not provide the TheMovieDbId
var movieDbResults = await _movieApi.SearchTv(tvdbshow.name, tvdbshow.premiered.Substring(0, 4));
var potential = movieDbResults.FirstOrDefault();
tvDbId = potential.Id;
// end guess
var providers = await _movieApi.GetTvWatchProviders(tvDbId, cancellationToken);
var providers = await _movieApi.GetTvWatchProviders(movieDbId, cancellationToken);
var results = await GetUserWatchProvider(providers);
var data = new List<StreamingData>();
@ -158,9 +139,9 @@ namespace Ombi.Core.Engine.V2
return _mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
}
private async Task<SearchFullInfoTvShowViewModel> ProcessResult(SearchFullInfoTvShowViewModel item, Task<TraktShow> showInfoTask)
private async Task<SearchFullInfoTvShowViewModel> ProcessResult(SearchFullInfoTvShowViewModel item)
{
item.TheTvDbId = item.Id.ToString();
item.TheMovieDbId = item.Id.ToString();
var oldModel = _mapper.Map<SearchTvShowViewModel>(item);
await RunSearchRules(oldModel);
@ -179,18 +160,9 @@ namespace Ombi.Core.Engine.V2
item.Images.Medium = item.Images.Medium.ToHttpsUrl();
}
if (item.Cast?.Any() ?? false)
{
foreach (var cast in item.Cast)
{
if (!string.IsNullOrEmpty(cast.Character?.Image?.Medium))
{
cast.Character.Image.Medium = cast.Character?.Image?.Medium.ToHttpsUrl();
}
}
}
return await GetExtraInfo(showInfoTask, item);
return item;
//return await GetExtraInfo(showInfoTask, item);
}
private async Task<SearchFullInfoTvShowViewModel> GetExtraInfo(Task<TraktShow> showInfoTask, SearchFullInfoTvShowViewModel model)

View file

@ -35,14 +35,17 @@ namespace Ombi.Core.Helpers
public TvRequests NewRequest { get; protected set; }
protected TvMazeShow ShowInfo { get; set; }
protected List<TvSearchResult> Results { get; set; }
protected TvSearchResult TheMovieDbRecord { get; set; }
public async Task<TvShowRequestBuilder> GetShowInfo(int id)
{
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
Results = await MovieDbApi.SearchTv(ShowInfo.name);
foreach (TvSearchResult result in Results) {
foreach (TvSearchResult result in Results)
{
if (result.Name.Equals(ShowInfo.name, StringComparison.InvariantCultureIgnoreCase))
{
TheMovieDbRecord = result;
var showIds = await MovieDbApi.GetTvExternals(result.Id);
ShowInfo.externals.imdb = showIds.imdb_id;
BackdropPath = result.BackdropPath;
@ -240,6 +243,7 @@ namespace Ombi.Core.Helpers
PosterPath = PosterPath,
Title = ShowInfo.name,
ReleaseDate = FirstAir,
ExternalProviderId = TheMovieDbRecord.Id,
Status = ShowInfo.status,
ImdbId = ShowInfo.externals?.imdb ?? string.Empty,
TvDbId = tv.TvDbId,

View file

@ -0,0 +1,247 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Models.Requests;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
using System.Threading;
namespace Ombi.Core.Helpers
{
public class TvShowRequestBuilderV2
{
public TvShowRequestBuilderV2(IMovieDbApi movApi)
{
MovieDbApi = movApi;
}
private IMovieDbApi MovieDbApi { get; }
public ChildRequests ChildRequest { get; set; }
public List<SeasonsViewModel> TvRequests { get; protected set; }
public string PosterPath { get; protected set; }
public string BackdropPath { get; protected set; }
public DateTime FirstAir { get; protected set; }
public TvRequests NewRequest { get; protected set; }
protected TvInfo TheMovieDbRecord { get; set; }
public async Task<TvShowRequestBuilderV2> GetShowInfo(int id)
{
TheMovieDbRecord = await MovieDbApi.GetTVInfo(id.ToString());
BackdropPath = TheMovieDbRecord.Images?.Backdrops?.OrderBy(x => x.VoteCount).ThenBy(x => x.VoteAverage).FirstOrDefault()?.FilePath; ;
DateTime.TryParse(TheMovieDbRecord.first_air_date, out var dt);
FirstAir = dt;
// For some reason the poster path is always http
PosterPath = TheMovieDbRecord.Images?.Posters?.OrderBy(x => x.VoteCount).ThenBy(x => x.VoteAverage).FirstOrDefault()?.FilePath;
return this;
}
public TvShowRequestBuilderV2 CreateChild(TvRequestViewModelV2 model, string userId)
{
var animationGenre = TheMovieDbRecord.genres?.Any(s => s.name.Equals("Animation", StringComparison.InvariantCultureIgnoreCase)) ?? false;
var animeKeyword = TheMovieDbRecord.Keywords?.KeywordsValue?.Any(s => s.Name.Equals("Anime", StringComparison.InvariantCultureIgnoreCase)) ?? false;
ChildRequest = new ChildRequests
{
Id = model.TheMovieDbId, // This is set to 0 after the request rules have run, the request rules needs it to identify the request
RequestType = RequestType.TvShow,
RequestedDate = DateTime.UtcNow,
Approved = false,
RequestedUserId = userId,
SeasonRequests = new List<SeasonRequests>(),
Title = TheMovieDbRecord.name,
ReleaseYear = FirstAir,
RequestedByAlias = model.RequestedByAlias,
SeriesType = animationGenre && animeKeyword ? SeriesType.Anime : SeriesType.Standard
};
return this;
}
public TvShowRequestBuilderV2 CreateTvList(TvRequestViewModelV2 tv)
{
TvRequests = new List<SeasonsViewModel>();
// Only have the TV requests we actually requested and not everything
foreach (var season in tv.Seasons)
{
if (season.Episodes.Any())
{
TvRequests.Add(season);
}
}
return this;
}
public async Task<TvShowRequestBuilderV2> BuildEpisodes(TvRequestViewModelV2 tv)
{
var allEpisodes = new List<Episode>();
foreach (var season in TheMovieDbRecord.seasons)
{
var seasonEpisodes = await MovieDbApi.GetSeasonEpisodes(TheMovieDbRecord.id, season.season_number, CancellationToken.None);
allEpisodes.AddRange(seasonEpisodes.episodes);
}
if (tv.RequestAll)
{
foreach (var ep in allEpisodes)
{
var season = ChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season_number);
if (season == null)
{
ChildRequest.SeasonRequests.Add(new SeasonRequests
{
Episodes = new List<EpisodeRequests>{
new EpisodeRequests
{
EpisodeNumber = ep.episode_number,
AirDate = FormatDate(ep.air_date),
Title = ep.name,
}
},
SeasonNumber = ep.season_number,
});
}
else
{
season.Episodes.Add(new EpisodeRequests
{
EpisodeNumber = ep.episode_number,
AirDate = FormatDate(ep.air_date),
Title = ep.name,
});
}
}
}
else if (tv.LatestSeason)
{
var latest = allEpisodes.OrderByDescending(x => x.season_number).FirstOrDefault();
var episodesRequests = new List<EpisodeRequests>();
foreach (var ep in allEpisodes)
{
if (ep.season_number == latest.season_number)
{
episodesRequests.Add(new EpisodeRequests
{
EpisodeNumber = ep.episode_number,
AirDate = FormatDate(ep.air_date),
Title = ep.name,
});
}
}
ChildRequest.SeasonRequests.Add(new SeasonRequests
{
Episodes = episodesRequests,
SeasonNumber = latest.season_number,
});
}
else if (tv.FirstSeason)
{
var first = allEpisodes.OrderBy(x => x.season_number).FirstOrDefault();
var episodesRequests = new List<EpisodeRequests>();
foreach (var ep in allEpisodes)
{
if (ep.season_number == first.season_number)
{
episodesRequests.Add(new EpisodeRequests
{
EpisodeNumber = ep.episode_number,
AirDate = FormatDate(ep.air_date),
Title = ep.name,
});
}
}
ChildRequest.SeasonRequests.Add(new SeasonRequests
{
Episodes = episodesRequests,
SeasonNumber = first.season_number,
});
}
else
{
// It's a custom request
var seasonRequests = new List<SeasonRequests>();
foreach (var ep in allEpisodes)
{
var existingSeasonRequest = seasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season_number);
if (existingSeasonRequest != null)
{
var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season_number);
var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.episode_number) ?? false;
if (requestedSeason != null && requestedEpisode)
{
// We already have this, let's just add the episodes to it
existingSeasonRequest.Episodes.Add(new EpisodeRequests
{
EpisodeNumber = ep.episode_number,
AirDate = FormatDate(ep.air_date),
Title = ep.name,
});
}
}
else
{
var newRequest = new SeasonRequests { SeasonNumber = ep.season_number };
var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season_number);
var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.episode_number) ?? false;
if (requestedSeason != null && requestedEpisode)
{
newRequest.Episodes.Add(new EpisodeRequests
{
EpisodeNumber = ep.episode_number,
AirDate = FormatDate(ep.air_date),
Title = ep.name,
});
seasonRequests.Add(newRequest);
}
}
}
foreach (var s in seasonRequests)
{
ChildRequest.SeasonRequests.Add(s);
}
}
return this;
}
public TvShowRequestBuilderV2 CreateNewRequest(TvRequestViewModelV2 tv)
{
int.TryParse(TheMovieDbRecord.ExternalIds?.TvDbId, out var tvdbId);
NewRequest = new TvRequests
{
Overview = TheMovieDbRecord.overview,
PosterPath = PosterPath,
Title = TheMovieDbRecord.name,
ReleaseDate = FirstAir,
ExternalProviderId = TheMovieDbRecord.id,
Status = TheMovieDbRecord.status,
ImdbId = TheMovieDbRecord.ExternalIds?.ImdbId ?? string.Empty,
TvDbId = tvdbId,
ChildRequests = new List<ChildRequests>(),
TotalSeasons = tv.Seasons.Count(),
Background = BackdropPath
};
NewRequest.ChildRequests.Add(ChildRequest);
return this;
}
private DateTime FormatDate(string date)
{
return string.IsNullOrEmpty(date) ? DateTime.MinValue : DateTime.Parse(date);
}
}
}

View file

@ -3,17 +3,9 @@ using Newtonsoft.Json;
namespace Ombi.Core.Models.Requests
{
public class TvRequestViewModel
public class TvRequestViewModel : TvRequestViewModelBase
{
public bool RequestAll { get; set; }
public bool LatestSeason { get; set; }
public bool FirstSeason { get; set; }
public int TvDbId { get; set; }
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
[JsonIgnore]
public string RequestedByAlias { get; set; }
public string RequestOnBehalf { get; set; }
}
public class SeasonsViewModel
@ -27,4 +19,16 @@ namespace Ombi.Core.Models.Requests
public int EpisodeNumber { get; set; }
}
public class TvRequestViewModelBase
{
public bool RequestAll { get; set; }
public bool LatestSeason { get; set; }
public bool FirstSeason { get; set; }
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
[JsonIgnore]
public string RequestedByAlias { get; set; }
public string RequestOnBehalf { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Ombi.Core.Models.Requests
{
public class TvRequestViewModelV2 : TvRequestViewModelBase
{
public int TheMovieDbId { get; set; }
}
}

View file

@ -14,7 +14,7 @@ namespace Ombi.Core.Models.Search.V2
public string FirstAired { get; set; }
public string NetworkId { get; set; }
public string Runtime { get; set; }
public List<string> Genre { get; set; }
public GenreViewModel[] Genres { get; set; }
public string Overview { get; set; }
public int LastUpdated { get; set; }
public string AirsDayOfWeek { get; set; }
@ -24,23 +24,12 @@ namespace Ombi.Core.Models.Search.V2
public NetworkViewModel Network { get; set; }
public Images Images { get; set; }
public List<CastViewModel> Cast { get; set; }
public List<CrewViewModel> Crew { get; set; }
public List<PersonViewModel> Crew { get; set; }
public string Certification { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Tagline { get; set; }
public Keywords Keywords { get; set; }
public ExternalIds ExternalIds { get; set; }
public string Trailer { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Homepage { get; set; }
public List<SeasonRequests> SeasonRequests { get; set; } = new List<SeasonRequests>();
@ -66,7 +55,7 @@ namespace Ombi.Core.Models.Search.V2
{
public int Id { get; set; }
public string Name { get; set; }
public Country Country { get; set; }
public string Country { get; set; }
}
public class Country
@ -84,8 +73,9 @@ namespace Ombi.Core.Models.Search.V2
public class CastViewModel
{
public PersonViewModel Person { get; set; }
public CharacterViewModel Character { get; set; }
public string Person { get; set; }
public string Character { get; set; }
public string Image { get; set; }
public bool Self { get; set; }
public bool Voice { get; set; }
}
@ -95,7 +85,7 @@ namespace Ombi.Core.Models.Search.V2
public int Id { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public Images Image { get; set; }
public string Image { get; set; }
}
public class CharacterViewModel
@ -106,9 +96,4 @@ namespace Ombi.Core.Models.Search.V2
public Images Image { get; set; }
}
public class CrewViewModel
{
public string Type { get; set; }
public PersonViewModel Person { get; set; }
}
}

View file

@ -32,8 +32,8 @@ namespace Ombi.Core.Rule.Rules.Request
var tvContent = _plexContent.GetAll().Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show);
// We need to do a check on the TVDBId
var anyTvDbMatches = await tvContent.FirstOrDefaultAsync(x => x.TvDbId.Length > 0 && x.TvDbId == tvRequest.Id.ToString()); // the Id on the child is the tvdbid at this point
if (anyTvDbMatches == null)
var anyMovieDbMatches = await tvContent.FirstOrDefaultAsync(x => x.TheMovieDbId.Length > 0 && x.TheMovieDbId == tvRequest.Id.ToString());
if (anyMovieDbMatches == null)
{
// So we do not have a TVDB Id, that really sucks.
// Let's try and match on the title and year of the show
@ -50,7 +50,7 @@ namespace Ombi.Core.Rule.Rules.Request
return Success();
}
// looks like we have a match on the TVDbID
return CheckExistingContent(tvRequest, anyTvDbMatches);
return CheckExistingContent(tvRequest, anyMovieDbMatches);
}
return Success();
}

View file

@ -28,7 +28,7 @@ namespace Ombi.Core.Rule.Rules.Request
{
var tv = (ChildRequests) obj;
var tvRequests = Tv.GetChild();
var currentRequest = await tvRequests.FirstOrDefaultAsync(x => x.ParentRequest.TvDbId == tv.Id); // the Id on the child is the tvdbid at this point
var currentRequest = await tvRequests.FirstOrDefaultAsync(x => x.ParentRequest.ExternalProviderId == tv.Id); // the Id on the child is the TheMovieDb at this point
if (currentRequest == null)
{
return Success();

View file

@ -22,7 +22,7 @@ namespace Ombi.Core.Rule.Rules
if (obj.RequestType == RequestType.TvShow)
{
var vm = (ChildRequests) obj;
var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId == vm.Id);
var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId == vm.Id); // TODO lookup the external provider in the sonarr sync to use themoviedb
if (result != null)
{
if (vm.SeasonRequests.Any())
@ -54,7 +54,7 @@ namespace Ombi.Core.Rule.Rules
{
var vm = (SearchTvShowViewModel) obj;
// Check if it's in Radarr
var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId == vm.Id);
var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId.ToString() == vm.TheTvDbId);
if (result != null)
{
vm.Approved = true;
@ -69,7 +69,7 @@ namespace Ombi.Core.Rule.Rules
// Check if we have it
var monitoredInSonarr = await sonarrEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == season.SeasonNumber
&& x.TvDbId == vm.Id);
&& x.TvDbId.ToString() == vm.TheTvDbId);
if (monitoredInSonarr != null)
{
ep.Approved = true;

View file

@ -140,7 +140,7 @@ namespace Ombi.Core.Senders
private async Task<DogNzbAddResult> SendToDogNzb(ChildRequests model, DogNzbSettings settings)
{
var id = model.ParentRequest.TvDbId;
var id = model.ParentRequest.ExternalProviderId;
return await DogNzbApi.AddTvShow(settings.ApiKey, id.ToString());
}

View file

@ -14,7 +14,7 @@ namespace Ombi.Mapping.Profiles
{
public MovieProfile()
{
CreateMap<SearchResult, MovieSearchResult>()
CreateMap<SearchResult, MovieDbSearchResult>()
.ForMember(dest => dest.Adult, opts => opts.MapFrom(src => src.adult))
.ForMember(dest => dest.BackdropPath, opts => opts.MapFrom(src => src.backdrop_path))
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
@ -24,7 +24,7 @@ namespace Ombi.Mapping.Profiles
.ForMember(dest => dest.Popularity, opts => opts.MapFrom(src => src.popularity))
.ForMember(dest => dest.PosterPath, opts => opts.MapFrom(src => src.poster_path))
.ForMember(dest => dest.ReleaseDate, opts => opts.MapFrom(src => src.release_date))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.title))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => string.IsNullOrEmpty(src.title) ? src.name : src.title))
.ForMember(dest => dest.Video, opts => opts.MapFrom(src => src.video))
.ForMember(dest => dest.VoteAverage, opts => opts.MapFrom(src => src.vote_average))
.ForMember(dest => dest.VoteCount, opts => opts.MapFrom(src => src.vote_count));
@ -75,7 +75,7 @@ namespace Ombi.Mapping.Profiles
CreateMap<TheMovieDbApi.Models.Genre, GenreDto>();
CreateMap<MovieSearchResult, SearchMovieViewModel>().ReverseMap();
CreateMap<MovieDbSearchResult, SearchMovieViewModel>().ReverseMap();
CreateMap<MovieResponseDto, SearchMovieViewModel>().ReverseMap();
CreateMap<FullMovieInfo, SearchMovieViewModel>().ReverseMap();

View file

@ -1,6 +1,7 @@
using System;
using System.Globalization;
using AutoMapper;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Api.TvMaze.Models;
using Ombi.Core.Models.Search;
using Ombi.Helpers;
@ -27,6 +28,15 @@ namespace Ombi.Mapping.Profiles
.ForMember(dest => dest.Banner, opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.show.image.medium) ? src.show.image.medium.ToHttpsUrl() : string.Empty))
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.show.status));
CreateMap<TvSearchResult, SearchTvShowViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id))
.ForMember(dest => dest.TheMovieDbId, opts => opts.MapFrom(src => src.Id.ToString()))
.ForMember(dest => dest.FirstAired, opts => opts.MapFrom(src => src.ReleaseDate))
.ForMember(dest => dest.Overview, opts => opts.MapFrom(src => src.Overview))
.ForMember(dest => dest.Rating, opts => opts.MapFrom(src => src.VoteAverage.ToString(CultureInfo.CurrentUICulture)))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.Name))
.ForMember(dest => dest.Banner, opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.BackdropPath) ? src.BackdropPath.ToHttpsUrl() : src.PosterPath));
CreateMap<TvMazeShow, SearchTvShowViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.externals.thetvdb))
.ForMember(dest => dest.FirstAired, opts => opts.MapFrom(src => src.premiered))
@ -60,6 +70,23 @@ namespace Ombi.Mapping.Profiles
opts => opts.MapFrom(src => src.Trailer != null ? src.Trailer.ToString().ToHttpsUrl() : string.Empty))
.ForMember(dest => dest.Homepage,
opts => opts.MapFrom(src => src.Homepage != null ? src.Homepage.ToString().ToHttpsUrl() : string.Empty));
CreateMap<MovieDbSearchResult, SearchTvShowViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id))
.ForMember(dest => dest.FirstAired, opts => opts.MapFrom(src => src.ReleaseDate))
//.ForMember(dest => dest.ImdbId, opts => opts.MapFrom(src => src.Ids.Imdb))
//.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.Network))
.ForMember(dest => dest.Overview, opts => opts.MapFrom(src => src.Overview))
.ForMember(dest => dest.Rating, opts => opts.MapFrom(src => src.VoteAverage.ToString()))
.ForMember(dest => dest.BackdropPath, opts => opts.MapFrom(src => src.PosterPath))
//.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.Runtime.ToString()))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.Title));
//.ForMember(dest => dest.Status, opts => opts.MapFrom(src => TraktEnumHelper.GetDescription(src.Status)))
//.ForMember(dest => dest.Trailer,
// opts => opts.MapFrom(src => src.Trailer != null ? src.Trailer.ToString().ToHttpsUrl() : string.Empty))
//.ForMember(dest => dest.Homepage,
// opts => opts.MapFrom(src => src.Homepage != null ? src.Homepage.ToString().ToHttpsUrl() : string.Empty));
}
}
}

View file

@ -1,5 +1,8 @@
using System.Globalization;
using System;
using System.Globalization;
using System.Linq;
using AutoMapper;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Api.TvMaze.Models.V2;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
@ -24,7 +27,7 @@ namespace Ombi.Mapping.Profiles
.ForMember(dest => dest.SeriesId, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.network))
.ForMember(dest => dest.Images, opts => opts.MapFrom(src => src.image))
//.ForMember(dest => dest.Images, opts => opts.MapFrom(src => src.image))
.ForMember(dest => dest.Cast, opts => opts.MapFrom(src => src._embedded.cast))
.ForMember(dest => dest.Crew, opts => opts.MapFrom(src => src._embedded.crew))
.ForMember(dest => dest.Banner,
@ -34,19 +37,65 @@ namespace Ombi.Mapping.Profiles
: string.Empty))
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status));
CreateMap<Network, NetworkViewModel>()
CreateMap<TvInfo, SearchFullInfoTvShowViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.FirstAired, opts => opts.MapFrom(src => src.first_air_date))
.ForMember(dest => dest.ImdbId, opts => opts.MapFrom(src => src.ExternalIds.ImdbId))
.ForMember(dest => dest.TheTvDbId, opts => opts.MapFrom(src => src.ExternalIds.TvDbId))
.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.networks.FirstOrDefault()))
.ForMember(dest => dest.NetworkId, opts => opts.MapFrom(src => src.networks.FirstOrDefault().id))
.ForMember(dest => dest.Overview, opts => opts.MapFrom(src => src.overview))
.ForMember(dest => dest.Rating,
opts => opts.MapFrom(src => src.vote_average.ToString(CultureInfo.CurrentUICulture)))
//.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.runtime.ToString()))
.ForMember(dest => dest.SeriesId, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.name))
//.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.network))
.ForMember(dest => dest.Images, opts => opts.MapFrom(src => src.Images))
.ForMember(dest => dest.Cast, opts => opts.MapFrom(src => src.Credits.cast))
.ForMember(dest => dest.Crew, opts => opts.MapFrom(src => src.Credits.crew))
.ForMember(dest => dest.Banner, opts => opts.MapFrom(src => GetBanner(src.Images)))
.ForMember(dest => dest.Genres, opts => opts.MapFrom(src => src.genres))
.ForMember(dest => dest.Keywords, opts => opts.MapFrom(src => src.Keywords))
.ForMember(dest => dest.Tagline, opts => opts.MapFrom(src => src.tagline))
.ForMember(dest => dest.ExternalIds, opts => opts.MapFrom(src => src.ExternalIds))
.ForMember(dest => dest.Trailer, opts => opts.MapFrom(src => GetTrailer(src.Videos)))
.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.episode_run_time.FirstOrDefault()))
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status));
CreateMap<Ombi.Api.TheMovieDb.Models.ExternalIds, Ombi.Core.Models.Search.V2.ExternalIds>().ReverseMap();
CreateMap<Ombi.Api.TheMovieDb.Models.Images, Ombi.Core.Models.Search.V2.Images>()
.ForMember(dest => dest.Original, opts => opts.MapFrom(src => src.Posters.OrderBy(x => x.VoteCount).ThenBy(x => x.VoteAverage).FirstOrDefault().FilePath));
CreateMap<Api.TheMovieDb.Models.Keywords, Ombi.Core.Models.Search.V2.Keywords>().ReverseMap();
CreateMap<Ombi.Api.TheMovieDb.Models.KeywordsValue, Ombi.Core.Models.Search.V2.KeywordsValue>().ReverseMap();
CreateMap<GenreViewModel, Genre>()
.ForMember(dest => dest.id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.name, opts => opts.MapFrom(src => src.name));
CreateMap<Api.TvMaze.Models.V2.Network, NetworkViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Country, opts => opts.MapFrom(src => src.country))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name));
CreateMap<Api.TheMovieDb.Models.Network, NetworkViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Country, opts => opts.MapFrom(src => src.origin_country))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name));
CreateMap<Api.TvMaze.Models.V2.Country, Core.Models.Search.V2.Country>()
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Code, opts => opts.MapFrom(src => src.code))
.ForMember(dest => dest.Timezone, opts => opts.MapFrom(src => src.timezone));
CreateMap<Api.TvMaze.Models.V2.Image, Images>()
.ForMember(dest => dest.Medium, opts => opts.MapFrom(src => src.medium.ToHttpsUrl()))
.ForMember(dest => dest.Original, opts => opts.MapFrom(src => src.original.ToHttpsUrl()));
//CreateMap<Api.TvMaze.Models.V2.Image, Images>()
// .ForMember(dest => dest.Medium, opts => opts.MapFrom(src => src.medium.ToHttpsUrl()))
// .ForMember(dest => dest.Original, opts => opts.MapFrom(src => src.original.ToHttpsUrl()));
CreateMap<Api.TvMaze.Models.V2.Cast, CastViewModel>()
.ForMember(dest => dest.Character, opts => opts.MapFrom(src => src.character))
@ -54,15 +103,25 @@ namespace Ombi.Mapping.Profiles
.ForMember(dest => dest.Voice, opts => opts.MapFrom(src => src.voice))
.ForMember(dest => dest.Self, opts => opts.MapFrom(src => src.self));
CreateMap<FullMovieCast, CastViewModel>()
.ForMember(dest => dest.Character, opts => opts.MapFrom(src => src.character))
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Image, opts => opts.MapFrom(src => src.profile_path));
CreateMap<Api.TvMaze.Models.V2.Person, PersonViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Image, opts => opts.MapFrom(src => src.image))
.ForMember(dest => dest.Url, opts => opts.MapFrom(src => src.url.ToHttpsUrl()));
CreateMap<Api.TvMaze.Models.V2.Crew, CrewViewModel>()
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
.ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.type));
//CreateMap<Api.TvMaze.Models.V2.Crew, CrewViewModel>()
// .ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
// .ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.type));
CreateMap<FullMovieCrew, PersonViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Image, opts => opts.MapFrom(src => src.profile_path))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name));
CreateMap<Api.TvMaze.Models.V2.Cast, CastViewModel>()
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
@ -78,5 +137,27 @@ namespace Ombi.Mapping.Profiles
CreateMap<SearchTvShowViewModel, SearchFullInfoTvShowViewModel>().ReverseMap();
}
private string GetBanner(Api.TheMovieDb.Models.Images images)
{
var hasBackdrop = images?.Backdrops?.Any();
if (hasBackdrop ?? false)
{
return images.Backdrops?.OrderBy(x => x.VoteCount).ThenBy(x => x.VoteAverage).Select(x => x.FilePath).FirstOrDefault();
}
else if (images != null)
{
return images.Posters?.OrderBy(x => x.VoteCount).ThenBy(x => x.VoteAverage).Select(x => x.FilePath).FirstOrDefault();
}
else
{
return string.Empty;
}
}
private string GetTrailer(Api.TheMovieDb.Models.Videos trailer)
{
return trailer?.results?.FirstOrDefault(x => x.type.Equals("trailer", StringComparison.InvariantCultureIgnoreCase))?.key ?? null;
}
}
}

View file

@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates
if (string.IsNullOrEmpty(_templateLocation))
{
#if DEBUG
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp3.1", "Templates",
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net5.0", "Templates",
"BasicTemplate.html");
#else
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html");

View file

@ -131,7 +131,7 @@ namespace Ombi.Notifications
{
LoadIssues(opts);
RequestId = req?.Id.ToString();
ProviderId = req?.ParentRequest?.TvDbId.ToString() ?? string.Empty;
ProviderId = req?.ParentRequest?.ExternalProviderId.ToString() ?? string.Empty;
string title;
if (req == null)
{

View file

@ -34,6 +34,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
@ -49,8 +51,9 @@ namespace Ombi.Schedule.Jobs.Plex
{
public class PlexContentSync : IPlexContentSync
{
private readonly IMovieDbApi _movieApi;
public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo,
IPlexEpisodeSync epsiodeSync, IHubContext<NotificationHub> hub)
IPlexEpisodeSync epsiodeSync, IHubContext<NotificationHub> hub, IMovieDbApi movieDbApi)
{
Plex = plex;
PlexApi = plexApi;
@ -58,6 +61,7 @@ namespace Ombi.Schedule.Jobs.Plex
Repo = repo;
EpisodeSync = epsiodeSync;
Notification = hub;
_movieApi = movieDbApi;
Plex.ClearCache();
}
@ -418,7 +422,7 @@ namespace Ombi.Schedule.Jobs.Plex
{
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
existingContent.Key);
GetProviderIds(showMetadata, existingContent);
await GetProviderIds(showMetadata, existingContent);
await Repo.Update(existingContent);
}
@ -531,7 +535,7 @@ namespace Ombi.Schedule.Jobs.Plex
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
Seasons = new List<PlexSeasonsContent>()
};
GetProviderIds(showMetadata, item);
await GetProviderIds(showMetadata, item);
// Let's just double check to make sure we do not have it now we have some id's
var existingImdb = false;
@ -573,7 +577,7 @@ namespace Ombi.Schedule.Jobs.Plex
}
}
private static void GetProviderIds(PlexMetadata showMetadata, PlexServerContent existingContent)
private async Task GetProviderIds(PlexMetadata showMetadata, PlexServerContent existingContent)
{
var metadata = showMetadata.MediaContainer.Metadata.FirstOrDefault();
var guids = new List<string>
@ -601,6 +605,13 @@ namespace Ombi.Schedule.Jobs.Plex
if (providerIds.TheTvDb.HasValue())
{
// Lookup TheMovieDbId
var findResult = await _movieApi.Find(providerIds.TheTvDb, ExternalSource.tvdb_id);
var tvResult = findResult.tv_results.FirstOrDefault();
if (tvResult != null)
{
existingContent.TheMovieDbId = tvResult.id.ToString();
}
existingContent.TvDbId = providerIds.TheTvDb;
}
}
@ -683,6 +694,7 @@ namespace Ombi.Schedule.Jobs.Plex
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)

View file

@ -4,7 +4,6 @@
{
public string BaseUrl { get; set; }
public bool CollectAnalyticData { get; set; }
public bool Set { get; set; }
public bool Wizard { get; set; }
public string ApiKey { get; set; }
public bool IgnoreCertificateErrors { get; set; }
@ -14,5 +13,9 @@
public string DefaultLanguageCode { get; set; } = "en";
public bool AutoDeleteAvailableRequests { get; set; }
public int AutoDeleteAfterDays { get; set; }
//INTERNAL
public bool HasMigratedOldTvDbData { get; set; }
public bool Set { get; set; }
}
}

View file

@ -11,6 +11,7 @@ namespace Ombi.Store.Repository.Requests
public class SeasonRequests : Entity
{
public int SeasonNumber { get; set; }
public string Overview { get; set; }
public List<EpisodeRequests> Episodes { get; set; } = new List<EpisodeRequests>();
public int ChildRequestId { get; set; }

View file

@ -6,7 +6,13 @@ namespace Ombi.Store.Entities.Requests
{
public class TvRequests : Entity
{
/// <summary>
/// No longer used since moved away from TvMaze
/// </summary>
public int TvDbId { get; set; }
// TheMovieDbId
public int ExternalProviderId { get; set; }
public string ImdbId { get; set; }
public int? QualityOverride { get; set; }
public int? RootFolder { get; set; }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ombi.Store.Migrations.OmbiMySql
{
public partial class TvRequestProviderId : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ExternalProviderId",
table: "TvRequests",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "Overview",
table: "SeasonRequests",
type: "longtext",
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "StreamingCountry",
table: "AspNetUsers",
type: "longtext",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ExternalProviderId",
table: "TvRequests");
migrationBuilder.DropColumn(
name: "Overview",
table: "SeasonRequests");
migrationBuilder.AlterColumn<string>(
name: "StreamingCountry",
table: "AspNetUsers",
type: "longtext",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext");
}
}
}

View file

@ -308,6 +308,7 @@ namespace Ombi.Store.Migrations.OmbiMySql
.HasColumnType("longtext");
b.Property<string>("StreamingCountry")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
@ -764,6 +765,9 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.Property<string>("Background")
.HasColumnType("longtext");
b.Property<int>("ExternalProviderId")
.HasColumnType("int");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
@ -954,6 +958,9 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.Property<int>("ChildRequestId")
.HasColumnType("int");
b.Property<string>("Overview")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ombi.Store.Migrations.OmbiSqlite
{
public partial class TvRequestProviderId : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ExternalProviderId",
table: "TvRequests",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "Overview",
table: "SeasonRequests",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ExternalProviderId",
table: "TvRequests");
migrationBuilder.DropColumn(
name: "Overview",
table: "SeasonRequests");
}
}
}

View file

@ -764,6 +764,9 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.Property<string>("Background")
.HasColumnType("TEXT");
b.Property<int>("ExternalProviderId")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
@ -954,6 +957,9 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.Property<int>("ChildRequestId")
.HasColumnType("INTEGER");
b.Property<string>("Overview")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");

View file

@ -16,7 +16,7 @@ namespace Ombi.Store.Repository.Requests
IQueryable<TvRequests> Get(string userId);
IQueryable<TvRequests> GetLite(string userId);
Task<TvRequests> GetRequestAsync(int tvDbId);
TvRequests GetRequest(int tvDbId);
TvRequests GetRequest(int theMovieDbId);
Task Update(TvRequests request);
Task UpdateChild(ChildRequests request);
IQueryable<ChildRequests> GetChild();

View file

@ -20,7 +20,7 @@ namespace Ombi.Store.Repository.Requests
public async Task<TvRequests> GetRequestAsync(int tvDbId)
{
return await Db.TvRequests.Where(x => x.TvDbId == tvDbId)
return await Db.TvRequests.Where(x => x.ExternalProviderId == tvDbId)
.Include(x => x.ChildRequests)
.ThenInclude(x => x.RequestedUser)
.Include(x => x.ChildRequests)
@ -29,9 +29,9 @@ namespace Ombi.Store.Repository.Requests
.FirstOrDefaultAsync();
}
public TvRequests GetRequest(int tvDbId)
public TvRequests GetRequest(int theMovieDbId)
{
return Db.TvRequests.Where(x => x.TvDbId == tvDbId).AsSplitQuery()
return Db.TvRequests.Where(x => x.ExternalProviderId == theMovieDbId).AsSplitQuery()
.Include(x => x.ChildRequests)
.ThenInclude(x => x.RequestedUser)
.Include(x => x.ChildRequests)

View file

@ -10,16 +10,20 @@ namespace Ombi.Api.TheMovieDb
{
Task<MovieResponseDto> GetMovieInformation(int movieId);
Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId, string langCode = "en");
Task<List<MovieSearchResult>> NowPlaying(string languageCode, int? page = null);
Task<List<MovieSearchResult>> PopularMovies(string languageCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken));
Task<List<MovieSearchResult>> SearchMovie(string searchTerm, int? year, string languageCode);
Task<List<MovieDbSearchResult>> NowPlaying(string languageCode, int? page = null);
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>> SearchMovie(string searchTerm, int? year, string languageCode);
Task<List<TvSearchResult>> SearchTv(string searchTerm, string year = default);
Task<List<MovieSearchResult>> TopRated(string languageCode, int? page = null);
Task<List<MovieSearchResult>> Upcoming(string languageCode, int? page = null);
Task<List<MovieSearchResult>> SimilarMovies(int movieId, string langCode);
Task<List<MovieDbSearchResult>> TopRated(string languageCode, int? page = null);
Task<List<MovieDbSearchResult>> Upcoming(string languageCode, int? page = null);
Task<List<MovieDbSearchResult>> TopRatedTv(string languageCode, int? page = null);
Task<List<MovieDbSearchResult>> UpcomingTv(string languageCode, int? page = null);
Task<List<MovieDbSearchResult>> SimilarMovies(int movieId, string langCode);
Task<FindResult> Find(string externalId, ExternalSource source);
Task<TvExternals> GetTvExternals(int theMovieDbId);
Task<TvInfo> GetTVInfo(string themoviedbid);
Task<SeasonDetails> GetSeasonEpisodes(int theMovieDbId, int seasonNumber, CancellationToken token, string langCode = "en");
Task<TvInfo> GetTVInfo(string themoviedbid, string langCode = "en");
Task<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode);
Task<ActorCredits> GetActorMovieCredits(int actorId, string langCode);
Task<TheMovieDbContainer<MultiSearch>> MultiSearch(string searchTerm, string languageCode, CancellationToken cancellationToken);

View file

@ -6,7 +6,12 @@
public object[] person_results { get; set; }
public TvResults[] tv_results { get; set; }
public object[] tv_episode_results { get; set; }
public object[] tv_season_results { get; set; }
public FindSeasonResults[] tv_season_results { get; set; }
}
public class FindSeasonResults
{
public int show_id { get; set; }
}
public class Movie_Results

View file

@ -87,6 +87,10 @@ namespace Ombi.Api.TheMovieDb.Models
{
[JsonProperty("file_path")]
public string FilePath { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("vote_average")]
public double VoteAverage { get; set; }
}
public class Keywords
@ -128,6 +132,8 @@ namespace Ombi.Api.TheMovieDb.Models
public string InstagramId { get; set; }
[JsonProperty("twitter_id")]
public string TwitterId { get; set; }
[JsonProperty("tvdb_id")]
public string TvDbId { get; set; }
}
public class Credits

View file

@ -1,6 +1,6 @@
namespace Ombi.Api.TheMovieDb.Models
{
public class MovieSearchResult
public class MovieDbSearchResult
{
public string PosterPath { get; set; }
public bool Adult { get; set; }

View file

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ombi.Api.TheMovieDb.Models
{
public class SeasonDetails
{
public string _id { get; set; }
public string air_date { get; set; }
public Episode[] episodes { get; set; }
public string name { get; set; }
public string overview { get; set; }
public int id { get; set; }
public string poster_path { get; set; }
public int season_number { get; set; }
}
public class Episode
{
public string air_date { get; set; }
public int episode_number { get; set; }
public Crew[] crew { get; set; }
public Guest_Stars[] guest_stars { get; set; }
public int id { get; set; }
public string name { get; set; }
public string overview { get; set; }
public string production_code { get; set; }
public int season_number { get; set; }
public string still_path { get; set; }
public float vote_average { get; set; }
public int vote_count { get; set; }
}
public class Guest_Stars
{
public string character { get; set; }
public string credit_id { get; set; }
public int order { get; set; }
public bool adult { get; set; }
public int gender { get; set; }
public int id { get; set; }
public string known_for_department { get; set; }
public string name { get; set; }
public string original_name { get; set; }
public float popularity { get; set; }
public string profile_path { get; set; }
}
}

View file

@ -18,6 +18,7 @@ namespace Ombi.Api.TheMovieDb.Models
public Network[] networks { get; set; }
public int number_of_episodes { get; set; }
public int number_of_seasons { get; set; }
public string tagline { get; set; }
public string[] origin_country { get; set; }
public string original_language { get; set; }
public string original_name { get; set; }
@ -30,7 +31,21 @@ namespace Ombi.Api.TheMovieDb.Models
public string type { get; set; }
public float vote_average { get; set; }
public int vote_count { get; set; }
[JsonProperty("external_ids")] public TvExternalIds TvExternalIds { get; set; }
[JsonProperty("videos")]
public Videos Videos { get; set; }
[JsonProperty("credits")]
public Credits Credits { get; set; }
[JsonProperty("similar")]
public Similar Similar { get; set; }
[JsonProperty("recommendations")]
public Recommendations Recommendations { get; set; }
[JsonProperty("external_ids")]
public ExternalIds ExternalIds { get; set; }
[JsonProperty("keywords")]
public Keywords Keywords { get; set; }
[JsonProperty("images")]
public Images Images { get; set; }
}
public class Created_By

View file

@ -132,7 +132,7 @@ namespace Ombi.Api.TheMovieDb
return await Api.Request<TvExternals>(request);
}
public async Task<List<MovieSearchResult>> SimilarMovies(int movieId, string langCode)
public async Task<List<MovieDbSearchResult>> SimilarMovies(int movieId, string langCode)
{
var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
@ -140,7 +140,7 @@ namespace Ombi.Api.TheMovieDb
AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieSearchResult>>(result.results);
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
}
public async Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId, string langCode = "en")
@ -154,7 +154,7 @@ namespace Ombi.Api.TheMovieDb
return Mapper.Map<MovieResponseDto>(result);
}
public async Task<List<MovieSearchResult>> SearchMovie(string searchTerm, int? year, string langCode)
public async Task<List<MovieDbSearchResult>> SearchMovie(string searchTerm, int? year, string langCode)
{
var request = new Request($"search/movie", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
@ -171,15 +171,25 @@ namespace Ombi.Api.TheMovieDb
AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieSearchResult>>(result.results);
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
}
/// <remarks>
/// Maintains filter parity with <a href="https://developers.themoviedb.org/3/movies/get-popular-movies">/movie/popular</a>.
/// </remarks>
public async Task<List<MovieSearchResult>> PopularMovies(string langCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken))
public async Task<List<MovieDbSearchResult>> PopularMovies(string langCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken))
{
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
return await Popular("movie", langCode, page, cancellationToken);
}
public async Task<List<MovieDbSearchResult>> PopularTv(string langCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken))
{
return await Popular("tv", langCode, page, cancellationToken);
}
public async Task<List<MovieDbSearchResult>> Popular(string type, string langCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken))
{
var request = new Request($"discover/{type}", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
request.AddQueryString("language", langCode);
request.AddQueryString("sort_by", "popularity.desc");
@ -187,18 +197,28 @@ namespace Ombi.Api.TheMovieDb
{
request.AddQueryString("page", page.ToString());
}
await AddDiscoverMovieSettings(request);
await AddDiscoverSettings(request);
AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request, cancellationToken);
return Mapper.Map<List<MovieSearchResult>>(result.results);
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
}
public Task<List<MovieDbSearchResult>> TopRated(string langCode, int? page = null)
{
return TopRated("movie", langCode, page);
}
public Task<List<MovieDbSearchResult>> TopRatedTv(string langCode, int? page = null)
{
return TopRated("tv", langCode, page);
}
/// <remarks>
/// Maintains filter parity with <a href="https://developers.themoviedb.org/3/movies/get-top-rated-movies">/movie/top_rated</a>.
/// </remarks>
public async Task<List<MovieSearchResult>> TopRated(string langCode, int? page = null)
private async Task<List<MovieDbSearchResult>> TopRated(string type, string langCode, int? page = null)
{
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
var request = new Request($"discover/{type}", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
request.AddQueryString("language", langCode);
request.AddQueryString("sort_by", "vote_average.desc");
@ -212,18 +232,27 @@ namespace Ombi.Api.TheMovieDb
// to filter out extremely high-rated movies due to very little votes
request.AddQueryString("vote_count.gte", "250");
await AddDiscoverMovieSettings(request);
await AddDiscoverSettings(request);
AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieSearchResult>>(result.results);
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
}
public Task<List<MovieDbSearchResult>> Upcoming(string langCode, int? page = null)
{
return Upcoming("movie", langCode, page);
}
public Task<List<MovieDbSearchResult>> UpcomingTv(string langCode, int? page = null)
{
return Upcoming("tv", langCode, page);
}
/// <remarks>
/// Maintains filter parity with <a href="https://developers.themoviedb.org/3/movies/get-upcoming">/movie/upcoming</a>.
/// </remarks>
public async Task<List<MovieSearchResult>> Upcoming(string langCode, int? page = null)
private async Task<List<MovieDbSearchResult>> Upcoming(string type, string langCode, int? page = null)
{
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
var request = new Request($"discover/{type}", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
request.AddQueryString("language", langCode);
@ -239,16 +268,16 @@ namespace Ombi.Api.TheMovieDb
{
request.AddQueryString("page", page.ToString());
}
await AddDiscoverMovieSettings(request);
await AddDiscoverSettings(request);
AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieSearchResult>>(result.results);
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
}
/// <remarks>
/// Maintains filter parity with <a href="https://developers.themoviedb.org/3/movies/get-now-playing">/movie/now_playing</a>.
/// </remarks>
public async Task<List<MovieSearchResult>> NowPlaying(string langCode, int? page = null)
public async Task<List<MovieDbSearchResult>> NowPlaying(string langCode, int? page = null)
{
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
@ -267,22 +296,33 @@ namespace Ombi.Api.TheMovieDb
request.AddQueryString("page", page.ToString());
}
await AddDiscoverMovieSettings(request);
await AddDiscoverSettings(request);
AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieSearchResult>>(result.results);
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
}
public async Task<TvInfo> GetTVInfo(string themoviedbid)
public async Task<TvInfo> GetTVInfo(string themoviedbid, string langCode = "en")
{
var request = new Request($"/tv/{themoviedbid}", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
request.AddQueryString("append_to_response", "external_ids");
request.AddQueryString("language", langCode);
request.AddQueryString("append_to_response", "videos,credits,similar,recommendations,external_ids,keywords,images");
AddRetry(request);
return await Api.Request<TvInfo>(request);
}
public async Task<SeasonDetails> GetSeasonEpisodes(int theMovieDbId, int seasonNumber, CancellationToken token, string langCode = "en")
{
var request = new Request($"/tv/{theMovieDbId}/season/{seasonNumber}", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
request.AddQueryString("language", langCode);
AddRetry(request);
return await Api.Request<SeasonDetails>(request, token);
}
public async Task<List<Keyword>> SearchKeyword(string searchTerm)
{
var request = new Request("search/keyword", BaseUri, HttpMethod.Get);
@ -330,7 +370,7 @@ namespace Ombi.Api.TheMovieDb
return Api.Request<WatchProviders>(request, token);
}
private async Task AddDiscoverMovieSettings(Request request)
private async Task AddDiscoverSettings(Request request)
{
var settings = await Settings;
request.AddQueryString("include_adult", settings.ShowAdultMovies.ToString().ToLower());

View file

@ -7,6 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi\Ombi.csproj",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9D30CCF8-A115-4EB7-A34D-07780D752789}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
..\.azuredevops\pipelines\templates\build-steps.yml = ..\.azuredevops\pipelines\templates\build-steps.yml
..\build.cake = ..\build.cake
..\CHANGELOG.md = ..\CHANGELOG.md
@ -121,7 +122,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Webhook", "Ombi.Ap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.CloudService", "Ombi.Api.CloudService\Ombi.Api.CloudService.csproj", "{5DE40A66-B369-469E-8626-ECE23D9D8034}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.RottenTomatoes", "Ombi.Api.RottenTomatoes\Ombi.Api.RottenTomatoes.csproj", "{8F19C701-7881-4BC7-8BBA-B068A6B954AD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.RottenTomatoes", "Ombi.Api.RottenTomatoes\Ombi.Api.RottenTomatoes.csproj", "{8F19C701-7881-4BC7-8BBA-B068A6B954AD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -29,7 +29,8 @@ export class DiscoverCardComponent implements OnInit {
public ngOnInit() {
if (this.result.type == RequestType.tvShow) {
this.getExtraTvInfo();
this.fullyLoaded = true;
// this.getExtraTvInfo();
}
if (this.result.type == RequestType.movie) {
this.getExtraMovieInfo();
@ -44,15 +45,15 @@ export class DiscoverCardComponent implements OnInit {
}
public async getExtraTvInfo() {
if (this.result.tvMovieDb) {
this.tvSearchResult = await this.searchService.getTvInfoWithMovieDbId(+this.result.id);
} else {
this.tvSearchResult = await this.searchService.getTvInfo(+this.result.id);
}
if (!this.tvSearchResult || this.tvSearchResult?.status.length > 0 && this.tvSearchResult?.status === "404") {
this.hide = true;
return;
}
// if (this.result.tvMovieDb) {
// this.tvSearchResult = await this.searchService.getTvInfoWithMovieDbId(+this.result.id);
// } else {
// this.tvSearchResult = await this.searchService.getTvInfo(+this.result.id);
// }
// if (!this.tvSearchResult || this.tvSearchResult?.status.length > 0 && this.tvSearchResult?.status === "404") {
// this.hide = true;
// return;
// }
this.setTvDefaults(this.tvSearchResult);
this.updateTvItem(this.tvSearchResult);

View file

@ -289,7 +289,7 @@ export class CarouselListComponent implements OnInit {
this.tvShows.forEach(m => {
tempResults.push({
available: m.available,
posterPath: "../../../images/default_tv_poster.png",
posterPath: m.backdropPath ? `https://image.tmdb.org/t/p/w500/${m.backdropPath}` : "../../../images/default_tv_poster.png",
requested: m.requested,
title: m.title,
type: RequestType.tvShow,

View file

@ -100,6 +100,7 @@ export interface IBaseRequest {
export interface ITvRequests {
id: number;
imdbId: string;
externalProviderId: number;
rootFolder: number;
overview: string;
title: string;
@ -111,7 +112,8 @@ export interface ITvRequests {
qualityOverride: number;
background: any;
totalSeasons: number;
tvDbId: number;
tvDbId: number; // NO LONGER USED
open: boolean; // THIS IS FOR THE UI
// For UI display
@ -146,6 +148,7 @@ export enum OrderType {
export interface INewSeasonRequests {
id: number;
overview: string;
seasonNumber: number;
episodes: IEpisodesRequests[];
seasonAvailable: boolean;

View file

@ -36,16 +36,21 @@ export interface ISearchTvResult {
subscribed: boolean;
showSubscribe: boolean;
fullyAvailable: boolean;
backdropPath: string;
partlyAvailable: boolean;
background: any;
open: boolean; // THIS IS FOR THE UI
}
export interface ITvRequestViewModel {
export interface ITvRequestViewModelV2 extends ITvRequestViewModelBase {
theMovieDbId: number;
}
export interface ITvRequestViewModelBase {
requestAll: boolean;
firstSeason: boolean;
latestSeason: boolean;
tvDbId: number;
seasons: ISeasonsViewModel[];
requestOnBehalf: string | undefined;
}

View file

@ -1,4 +1,5 @@
import { INewSeasonRequests, RequestType } from "./IRequestModel";
import { IExternalIds, IGenresViewModel, IKeywords } from "./ISearchMovieResultV2";
export interface ISearchTvResultV2 {
id: number;
@ -11,7 +12,7 @@ export interface ISearchTvResultV2 {
firstAired: string;
networkId: string;
runtime: string;
genre: string[];
genres: IGenresViewModel[],
overview: string;
lastUpdated: number;
airsDayOfWeek: string;
@ -21,7 +22,6 @@ export interface ISearchTvResultV2 {
siteRating: number;
trailer: string;
homepage: string;
certification: string;
seasonRequests: INewSeasonRequests[];
requestAll: boolean;
approved: boolean;
@ -30,6 +30,7 @@ export interface ISearchTvResultV2 {
plexUrl: string;
embyUrl: string;
jellyfinUrl: string;
tagline: string;
quality: string;
firstSeason: boolean;
latestSeason: boolean;
@ -38,8 +39,10 @@ export interface ISearchTvResultV2 {
showSubscribe: boolean;
fullyAvailable: boolean;
partlyAvailable: boolean;
externalIds: IExternalIds;
network: INetwork;
images: IImagesV2;
keywords: IKeywords;
cast: ICast[];
crew: ICrew[];
requestId: number;

View file

@ -8,19 +8,18 @@
<a *ngIf="item.profile_path" [routerLink]="'/discover/actor/' + item.id">
<img class="cast-profile-img" src="https://image.tmdb.org/t/p/w300/{{item.profile_path}}">
</a>
<a *ngIf="item.character?.image?.medium" [routerLink]="'/discover/actor/' + item.person.id">
<img class="cast-profile-img" [src]="item.character.image.medium">
<a *ngIf="item.image" [routerLink]="'/discover/actor/' + item.person.id">
<img class="cast-profile-img" src="https://image.tmdb.org/t/p/w300/{{item.image}}">
</a>
<!-- TODO get profile image default -->
</div>
<div class="col-12">
<span *ngIf="item.name"><strong>{{item.name}}</strong></span>
<span *ngIf="item.person?.name"><strong>{{item.person.name}}</strong></span>
<span *ngIf="item.person"><strong>{{item.person}}</strong></span>
</div>
<div class="col-12">
<span *ngIf="!item.character?.name"><small>{{item.character}}</small></span>
<span *ngIf="item.character.name"><small>{{item.character.name}}</small></span>
<span *ngIf="item.character"><small>{{item.character}}</small></span>
</div>
</div>

View file

@ -1,8 +1,9 @@
<div class="social-icons-container-inner">
<a *ngIf="homepage" class="media-icons" href="{{homepage}}" target="_blank">
<i matTooltip="Homepage" class="sfa-home fa-2x grow-social"></i>
</a>
<a *ngIf="theMoviedbId" href="https://www.themoviedb.org/movie/{{theMoviedbId}}" class="media-icons"
<a *ngIf="theMoviedbId" href="https://www.themoviedb.org/{{type === RequestType.movie ? 'movie' : 'tv'}}/{{theMoviedbId}}" class="media-icons"
target="_blank">
<i matTooltip="The Movie DB" class="fas fa-film fa-2x grow-social"></i>
</a>

View file

@ -1,4 +1,4 @@
import { Component, Inject, Input } from "@angular/core";
import { Component, Input } from "@angular/core";
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
@Component({

View file

@ -2,7 +2,7 @@
<div>
<div class="rating medium-font">
<span *ngIf="tv.rating">
<img class="rating-small" src="{{baseUrl}}/images/tvm-logo.png"> {{tv.rating}}/10
<img class="rating-small" src="{{baseUrl}}/images/tmdb-logo.svg"> {{tv.rating * 10}}%
</span>
<span *ngIf="ratings?.score && ratings?.class">
<img class="rating-small" src="{{baseUrl}}/images/{{ratings.class === 'rotten' ? 'rotten-rotten.svg' : 'rotten-fresh.svg'}}"> {{ratings.score}}%
@ -56,13 +56,27 @@
{{tv.network.name}}
</div>
<div *ngIf="tv.genre">
<div class="genre-button-container" *ngIf="tv.genres">
<span class="label">{{'MediaDetails.Genres' | translate }}:</span>
<div>
<span *ngFor="let genre of tv.genre">
{{genre}} |
</span>
<mat-chip-list>
<mat-chip selected *ngFor="let genre of tv.genres">
{{genre.name}}
</mat-chip>
</mat-chip-list>
</div>
</div>
<hr />
<div class="keyword-button-container" *ngIf="tv?.keywords?.keywordsValue?.length > 0">
<span class="label">{{'MediaDetails.Keywords' | translate }}:</span>
<mat-chip-list>
<mat-chip selected *ngFor="let keyword of tv.keywords.keywordsValue">
{{keyword.name}}
</mat-chip>
</mat-chip-list>
</div>
</div>

View file

@ -29,7 +29,7 @@ export class TvInformationPanelComponent implements OnInit {
this.searchService.getRottenTvRatings(this.tv.title, +this.tv.firstAired.toString().substring(0,4))
.subscribe(x => this.ratings = x);
this.searchService.getTvStreams(+this.tv.theTvDbId, this.tv.id).subscribe(x => this.streams = x);
this.searchService.getTvStreams(+this.tv.id ).subscribe(x => this.streams = x);
this.tv.seasonRequests.forEach(season => {
this.totalEpisodes = this.totalEpisodes + season.episodes.length;
});

View file

@ -8,6 +8,13 @@
</div>
</ng-template>
<mat-card class="mat-elevation-z8">
<mat-card-content>
<p>{{season.overview}}</p>
</mat-card-content>
</mat-card>
<table mat-table [dataSource]="season.episodes" class="mat-elevation-z8">
<ng-container matColumnDef="select">

View file

@ -1,11 +1,12 @@
import { Component, Input } from "@angular/core";
import { IChildRequests, IEpisodesRequests, INewSeasonRequests, ISeasonsViewModel, ITvRequestViewModel, RequestType } from "../../../../../interfaces";
import { IChildRequests, IEpisodesRequests, INewSeasonRequests, ISeasonsViewModel, ITvRequestViewModelV2, RequestType } from "../../../../../interfaces";
import { RequestService } from "../../../../../services/request.service";
import { MessageService } from "../../../../../services";
import { DenyDialogComponent } from "../../../shared/deny-dialog/deny-dialog.component";
import { ISearchTvResultV2 } from "../../../../../interfaces/ISearchTvResultV2";
import { MatDialog } from "@angular/material/dialog";
import { SelectionModel } from "@angular/cdk/collections";
import { RequestServiceV2 } from "../../../../../services/requestV2.service";
@Component({
templateUrl: "./tv-request-grid.component.html",
@ -24,7 +25,7 @@ export class TvRequestGridComponent {
public displayedColumns: string[] = ['select', 'number', 'title', 'airDate', 'status'];
constructor(private requestService: RequestService, private notificationService: MessageService,
constructor(private requestService: RequestService, private requestServiceV2: RequestServiceV2, private notificationService: MessageService,
private dialog: MatDialog) {
}
@ -39,8 +40,8 @@ export class TvRequestGridComponent {
this.tv.requested = true;
const viewModel = <ITvRequestViewModel>{
firstSeason: this.tv.firstSeason, latestSeason: this.tv.latestSeason, requestAll: this.tv.requestAll, tvDbId: this.tv.id,
const viewModel = <ITvRequestViewModelV2>{
firstSeason: this.tv.firstSeason, latestSeason: this.tv.latestSeason, requestAll: this.tv.requestAll, theMovieDbId: this.tv.id,
requestOnBehalf: null
};
viewModel.seasons = [];
@ -58,7 +59,7 @@ export class TvRequestGridComponent {
viewModel.seasons.push(seasonsViewModel);
});
const requestResult = await this.requestService.requestTv(viewModel).toPromise();
const requestResult = await this.requestServiceV2.requestTv(viewModel).toPromise();
if (requestResult.result) {
this.notificationService.send(

View file

@ -1,5 +1,4 @@
<mat-accordion class="mat-elevation-z8">
<mat-expansion-panel *ngFor="let request of tvRequest">
<mat-expansion-panel-header>
<mat-panel-title>

View file

@ -15,12 +15,15 @@
<div>
<top-banner [background]="tv.background" [available]="tv.available" [title]="tv.title"
[releaseDate]="tv.firstAired" [tagline]="tv.certification"></top-banner>
[releaseDate]="tv.firstAired" [tagline]="tv.tagline"></top-banner>
<div class="social-icons-container">
<social-icons
[homepage]="tv.homepage"
[tvdbId]="tv.id"
[theMoviedbId]="tv.id"
[hasTrailer]="tv.trailer"
[twitter]="tv.externalIds.twitterId"
[facebook]="tv.externalIds.facebookId"
[instagram]="tv.externalIds.instagramId"
(openTrailer)="openDialog()"
[imdbId]="tv.imdbId"
[available]="tv.available || tv.partlyAvailable"
@ -41,7 +44,7 @@
<div class="small-middle-container">
<div class="row justify-content-center justify-content-sm-start header-container">
<div class="details-poster-container">
<media-poster [posterPath]="tv.images?.medium"></media-poster>
<media-poster [posterPath]="'https://image.tmdb.org/t/p/w300/' + tv.images.original"></media-poster>
</div>
<!--Next to poster-->
<div class="details-button-container">

View file

@ -13,6 +13,7 @@ import { TvAdvancedOptionsComponent } from "./panels/tv-advanced-options/tv-adva
import { RequestServiceV2 } from "../../../services/requestV2.service";
import { RequestBehalfComponent } from "../shared/request-behalf/request-behalf.component";
import { forkJoin } from "rxjs";
import { TopBannerComponent } from "../shared/top-banner/top-banner.component";
@Component({
templateUrl: "./tv-details.component.html",
@ -57,12 +58,12 @@ export class TvDetailsComponent implements OnInit {
this.showAdvanced = await this.sonarrService.isEnabled();
}
if (this.fromSearch) {
this.tv = await this.searchService.getTvInfoWithMovieDbId(this.tvdbId);
this.tvdbId = this.tv.id;
} else {
// if (this.fromSearch) {
// this.tv = await this.searchService.getTvInfoWithMovieDbId(this.tvdbId);
// this.tvdbId = this.tv.id;
// } else {
this.tv = await this.searchService.getTvInfo(this.tvdbId);
}
// }
if (this.tv.requestId) {
this.tvRequest = await this.requestService.getChildRequests(this.tv.requestId).toPromise();
@ -70,8 +71,8 @@ export class TvDetailsComponent implements OnInit {
this.loadAdvancedInfo();
}
const tvBanner = await this.imageService.getTvBanner(this.tvdbId).toPromise();
this.tv.background = this.sanitizer.bypassSecurityTrustStyle("url(" + tvBanner + ")");
// const tvBanner = await this.imageService.getTvBanner(this.tvdbId).toPromise();
this.tv.background = this.sanitizer.bypassSecurityTrustStyle("url(https://image.tmdb.org/t/p/original" + this.tv.banner + ")");
}
public async request(userId: string) {
@ -87,7 +88,6 @@ export class TvDetailsComponent implements OnInit {
public openDialog() {
let trailerLink = this.tv.trailer;
trailerLink = trailerLink.split('?v=')[1];
this.dialog.open(YoutubeTrailerComponent, {
width: '560px',

View file

@ -210,7 +210,7 @@
}
.rating-small {
width: 1.3em;
width: 1.4em;
}
.stream-small {
width: 3em;

View file

@ -7,7 +7,6 @@ import { Observable } from "rxjs";
import { UITreeNode } from "primeng/tree";
import { FilterType, IAlbumRequest, IAlbumRequestModel, IAlbumUpdateModel, IChildRequests, IDenyAlbumModel, IDenyMovieModel, IFilter,
IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvDenyModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces";
import { ITvRequestViewModel } from "../interfaces";
import { ServiceHelpers } from "./service.helpers";
import { IRemainingRequests } from "../interfaces/IRemainingRequests";
@ -42,10 +41,6 @@ export class RequestService extends ServiceHelpers {
return this.http.get<number>(`${this.url}tv/total`, {headers: this.headers});
}
public requestTv(tv: ITvRequestViewModel): Observable<IRequestEngineResult> {
return this.http.post<IRequestEngineResult>(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers});
}
public approveMovie(movie: IMovieUpdateModel): Observable<IRequestEngineResult> {
return this.http.post<IRequestEngineResult>(`${this.url}Movie/Approve`, JSON.stringify(movie), {headers: this.headers});
}

View file

@ -4,7 +4,7 @@ import { Injectable, Inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
import { ServiceHelpers } from "./service.helpers";
import { IRequestsViewModel, IMovieRequests, IChildRequests, IMovieAdvancedOptions as IMediaAdvancedOptions, IRequestEngineResult, IAlbumRequest } from "../interfaces";
import { IRequestsViewModel, IMovieRequests, IChildRequests, IMovieAdvancedOptions as IMediaAdvancedOptions, IRequestEngineResult, IAlbumRequest, ITvRequestViewModelV2 } from "../interfaces";
@Injectable()
@ -88,4 +88,8 @@ export class RequestServiceV2 extends ServiceHelpers {
public getAlbumDeniedRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IAlbumRequest>> {
return this.http.get<IRequestsViewModel<IAlbumRequest>>(`${this.url}Album/denied/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
}
public requestTv(tv: ITvRequestViewModelV2): Observable<IRequestEngineResult> {
return this.http.post<IRequestEngineResult>(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers});
}
}

View file

@ -140,8 +140,8 @@ export class SearchV2Service extends ServiceHelpers {
return this.http.get<IStreamingData[]>(`${this.url}/stream/movie/${theMovieDbId}`);
}
public getTvStreams(theTvDbId: number, tvMaze: number): Observable<IStreamingData[]> {
return this.http.get<IStreamingData[]>(`${this.url}/stream/tv/${theTvDbId}/${tvMaze}`);
public getTvStreams(movieDbId: number): Observable<IStreamingData[]> {
return this.http.get<IStreamingData[]>(`${this.url}/stream/tv/${movieDbId}`);
}
}

View file

@ -1,10 +1,10 @@
import { Component, OnInit, Inject } from "@angular/core";
import { Component, Inject } from "@angular/core";
import { MatCheckboxChange } from "@angular/material/checkbox";
import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
import { ISearchTvResultV2 } from "../../interfaces/ISearchTvResultV2";
import { RequestService, MessageService } from "../../services";
import { ITvRequestViewModel, ISeasonsViewModel, IEpisodesRequests, INewSeasonRequests } from "../../interfaces";
import { ThousandShortPipe } from "../../pipes/ThousandShortPipe";
import { MessageService } from "../../services";
import { ISeasonsViewModel, IEpisodesRequests, INewSeasonRequests, ITvRequestViewModelV2 } from "../../interfaces";
import { RequestServiceV2 } from "../../services/requestV2.service";
export interface EpisodeRequestData {
series: ISearchTvResultV2;
@ -21,7 +21,7 @@ export class EpisodeRequestComponent {
}
constructor(public dialogRef: MatDialogRef<EpisodeRequestComponent>, @Inject(MAT_DIALOG_DATA) public data: EpisodeRequestData,
private requestService: RequestService, private notificationService: MessageService) { }
private requestService: RequestServiceV2, private notificationService: MessageService) { }
public async submitRequests() {
@ -31,7 +31,7 @@ export class EpisodeRequestComponent {
return ep.selected;
});
});
debugger;
if (!selected && !this.data.series.requestAll && !this.data.series.firstSeason && !this.data.series.latestSeason) {
this.notificationService.send("You need to select some episodes!", "OK");
return;
@ -39,8 +39,8 @@ export class EpisodeRequestComponent {
this.data.series.requested = true;
const viewModel = <ITvRequestViewModel>{
firstSeason: this.data.series.firstSeason, latestSeason: this.data.series.latestSeason, requestAll: this.data.series.requestAll, tvDbId: this.data.series.id,
const viewModel = <ITvRequestViewModelV2>{
firstSeason: this.data.series.firstSeason, latestSeason: this.data.series.latestSeason, requestAll: this.data.series.requestAll, theMovieDbId: this.data.series.id,
requestOnBehalf: this.data.requestOnBehalf
};
viewModel.seasons = [];

View file

@ -263,9 +263,9 @@ namespace Ombi.Controllers.V1
[HttpGet("tv/info/{tvdbId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<SearchTvShowViewModel> GetShowInfo(int tvdbId)
public async Task<SearchTvShowViewModel> GetShowInfo(string tvdbId)
{
return await TvEngine.GetShowInformation(tvdbId);
return await TvEngine.GetShowInformation(tvdbId, HttpContext.RequestAborted);
}
/// <summary>

View file

@ -1,14 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Ombi.Core;
using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.UI;
using Ombi.Store.Entities.Requests;
using System;
using Ombi.Store.Entities;
using System.Linq;
using Microsoft.Extensions.Logging;
namespace Ombi.Controllers.V2
{
@ -18,12 +20,17 @@ namespace Ombi.Controllers.V2
private readonly IMovieRequestEngine _movieRequestEngine;
private readonly ITvRequestEngine _tvRequestEngine;
private readonly IMusicRequestEngine _musicRequestEngine;
private readonly IVoteEngine _voteEngine;
private readonly ILogger<RequestsController> _logger;
public RequestsController(IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IMusicRequestEngine musicRequestEngine)
public RequestsController(IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IMusicRequestEngine musicRequestEngine,
IVoteEngine voteEngine, ILogger<RequestsController> logger)
{
_movieRequestEngine = movieRequestEngine;
_tvRequestEngine = tvRequestEngine;
_musicRequestEngine = musicRequestEngine;
_voteEngine = voteEngine;
_logger = logger;
}
/// <summary>
@ -168,5 +175,41 @@ namespace Ombi.Controllers.V2
{
return await _musicRequestEngine.GetRequests(count, position, sort, sortOrder);
}
/// <summary>
/// Requests a tv show/episode/season.
/// </summary>
/// <param name="tv">The tv.</param>
/// <returns></returns>
[HttpPost("tv")]
public async Task<RequestEngineResult> RequestTv([FromBody] TvRequestViewModelV2 tv)
{
tv.RequestedByAlias = GetApiAlias();
var result = await _tvRequestEngine.RequestTvShow(tv);
if (result.Result)
{
var voteResult = await _voteEngine.UpVote(result.RequestId, RequestType.TvShow);
if (voteResult.IsError)
{
_logger.LogError("Couldn't automatically add the vote for the tv {0} because {1}", tv.TheMovieDbId, voteResult.ErrorMessage);
}
}
return result;
}
private string GetApiAlias()
{
// Make sure this only applies when using the API KEY
if (HttpContext.Request.Headers.Keys.Contains("ApiKey", StringComparer.InvariantCultureIgnoreCase))
{
if (HttpContext.Request.Headers.TryGetValue("ApiAlias", out var apiAlias))
{
return apiAlias;
}
}
return null;
}
}
}

View file

@ -99,9 +99,9 @@ namespace Ombi.Controllers.V2
/// <remarks>TVMaze is the TV Show Provider</remarks>
/// <param name="tvdbid">The TVDB Id</param>
[HttpGet("tv/{tvdbId}")]
public async Task<SearchFullInfoTvShowViewModel> GetTvInfo(int tvdbid)
public async Task<SearchFullInfoTvShowViewModel> GetTvInfo(string tvdbid)
{
return await _tvEngineV2.GetShowInformation(tvdbid);
return await _tvEngineV2.GetShowInformation(tvdbid, HttpContext.RequestAborted);
}
/// <summary>
@ -112,7 +112,7 @@ namespace Ombi.Controllers.V2
[HttpGet("tv/request/{requestId}")]
public async Task<SearchFullInfoTvShowViewModel> GetTvInfoByRequest(int requestId)
{
return await _tvEngineV2.GetShowByRequest(requestId);
return await _tvEngineV2.GetShowByRequest(requestId, HttpContext.RequestAborted);
}
/// <summary>
@ -120,10 +120,9 @@ namespace Ombi.Controllers.V2
/// </summary>
/// <returns></returns>
[HttpGet("tv/moviedb/{moviedbid}")]
public async Task<SearchFullInfoTvShowViewModel> GetTvInfoByMovieId(int moviedbid)
public async Task<SearchFullInfoTvShowViewModel> GetTvInfoByMovieId(string moviedbid)
{
var tvDbId = await _movieEngineV2.GetTvDbId(moviedbid);
return await _tvEngineV2.GetShowInformation(tvDbId);
return await _tvEngineV2.GetShowInformation(moviedbid, HttpContext.RequestAborted);
}
/// <summary>
@ -437,12 +436,12 @@ namespace Ombi.Controllers.V2
return _movieEngineV2.GetStreamInformation(movieDBId, HttpContext.RequestAborted);
}
[HttpGet("stream/tv/{tvdbId}/{tvMaze}")]
[HttpGet("stream/tv/{movieDbId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public Task<IEnumerable<StreamingData>> GetTvStreams(int tvdbId, int tvMaze)
public Task<IEnumerable<StreamingData>> GetTvStreams(int movieDbId)
{
return _tvEngineV2.GetStreamInformation(tvdbId, tvMaze, HttpContext.RequestAborted);
return _tvEngineV2.GetStreamInformation(movieDbId, HttpContext.RequestAborted);
}
}
}

View file

@ -17,6 +17,10 @@ using Newtonsoft.Json;
using Ombi.Settings.Settings.Models;
using System.Diagnostics;
using System.IO;
using Ombi.Api.TheMovieDb;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb.Models;
namespace Ombi
{
@ -127,6 +131,9 @@ namespace Ombi
}
await SortOutBaseUrl(baseUrl, settingsDb, ombiSettingsContent);
var httpClient = new Ombi.Api.OmbiHttpClient(null, null);
var api = new Ombi.Api.Api(new Logger<Api.Api>(NullLoggerFactory.Instance), httpClient);
await MigrateOldTvDbIds(ombiDb, ombiSettingsContent, settingsDb, new Ombi.Api.TheMovieDb.TheMovieDbApi(null, (Api.IApi)api, null));
Console.WriteLine($"We are running on {urlValue}");
@ -283,6 +290,70 @@ namespace Ombi
Console.WriteLine($"Wrote new baseurl at {indexPath}");
}
}
private static async Task MigrateOldTvDbIds(OmbiContext ctx, GlobalSettings ombiSettingsContent, SettingsContext settingsContext, Api.TheMovieDb.TheMovieDbApi api)
{
var ombiSettings = JsonConvert.DeserializeObject<OmbiSettings>(ombiSettingsContent.Content);
if (ombiSettings.HasMigratedOldTvDbData)
{
return;
}
var tvRequests = ctx.TvRequests;
Console.WriteLine($"Total Requests to migrate {await tvRequests.CountAsync()}");
foreach (var request in tvRequests)
{
var findResuilt = await api.Find(request.TvDbId.ToString(), ExternalSource.tvdb_id);
var found = findResuilt.tv_results.FirstOrDefault();
if (found == null)
{
var seriesFound = findResuilt.tv_season_results.FirstOrDefault();
if (seriesFound == null)
{
Console.WriteLine($"Cannot find TheMovieDb Record for request {request.Title}");
continue;
}
request.ExternalProviderId = seriesFound.show_id;
Console.WriteLine($"Cannot find TheMovieDb Record for request, found potential match Title: {request.Title}, Match: {seriesFound.show_id}");
}
else
{
request.ExternalProviderId = found.id;
}
}
Console.WriteLine($"Finished Migration");
var strat = ctx.Database.CreateExecutionStrategy();
await strat.ExecuteAsync(async () =>
{
using (var tran = await ctx.Database.BeginTransactionAsync())
{
ctx.TvRequests.UpdateRange(tvRequests);
await ctx.SaveChangesAsync();
await tran.CommitAsync();
}
});
var settingsStrat = settingsContext.Database.CreateExecutionStrategy();
await settingsStrat.ExecuteAsync(async () =>
{
using (var tran = await settingsContext.Database.BeginTransactionAsync())
{
ombiSettings.HasMigratedOldTvDbData = true;
ombiSettingsContent.Content = JsonConvert.SerializeObject(ombiSettings);
settingsContext.Update(ombiSettingsContent);
await settingsContext.SaveChangesAsync();
await tran.CommitAsync();
}
});
return;
}
}
public class Options

View file

@ -148,7 +148,6 @@ namespace Ombi
// Generate a API Key
settings.ApiKey = Guid.NewGuid().ToString("N");
settings.CollectAnalyticData = true; // Since this is a first setup, enable analytical data collection
settings.Set = true;
ombiService.SaveSettings(settings);
}
@ -157,9 +156,6 @@ namespace Ombi
app.UsePathBase(settings.BaseUrl);
}
// Setup the scheduler
//var jobSetup = app.ApplicationServices.GetService<IJobSetup>();
//jobSetup.Setup();
ctx.Seed();
var settingsctx = serviceProvider.GetService<SettingsContext>();
settingsctx.Seed();