mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-31 03:50:08 -07:00
commit
472ecdbc3a
21 changed files with 358 additions and 75 deletions
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -6,6 +6,43 @@
|
|||
|
||||
## v3.0.3919 (2018-10-18)
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Updated the emby api since we no longer need the extra parameters to send to emby to log in a local user #2546. [Jamie]
|
||||
|
||||
- Added the ability to get the ombi user via a Plex Token #2591. [Jamie]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- Made the subscribe/unsubscribe button more obvious on the UI #2309. [Jamie]
|
||||
|
||||
- Fixed #2603. [Jamie]
|
||||
|
||||
- Fixed the issue with the user overrides #2646. [Jamie]
|
||||
|
||||
- Fixed the issue where we could sometimes allow the request of a whole series when the user shouldn't be able to. [Jamie]
|
||||
|
||||
- Fixed the issue where we were marking episodes as available with the Emby connection when they have not yet aired #2417 #2623. [TidusJar]
|
||||
|
||||
- Fixed the issue where we were marking the whole season as wanted in Sonarr rather than the individual episode #2629. [TidusJar]
|
||||
|
||||
- Fixed #2623. [Jamie]
|
||||
|
||||
- Fixed #2633. [TidusJar]
|
||||
|
||||
- Fixed #2639. [Jamie]
|
||||
|
||||
- Show the TV show as available when we have all the episodes but future episodes have not aired. #2585. [Jamie]
|
||||
|
||||
|
||||
## v3.0.3945 (2018-10-25)
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Update Readme for Lidarr. [Qstick]
|
||||
|
||||
- Update CHANGELOG.md. [Jamie]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- New translations en.json (French) [Jamie]
|
||||
|
|
12
appveyor.yml
12
appveyor.yml
|
@ -3,7 +3,11 @@ configuration: Release
|
|||
os: Visual Studio 2017
|
||||
environment:
|
||||
nodejs_version: "9.8.0"
|
||||
typescript_version: "3.0.1"
|
||||
typescript_version: "3.0.1"
|
||||
github_auth_token:
|
||||
secure: H/7uCrjmWHGJxgN3l9fbhhdVjvvWI8VVF4ZzQqeXuJwAf+PgSNBdxv4SS+rMQ+RH
|
||||
sonarrcloudtoken:
|
||||
secure: WGkIog4wuMSx1q5vmSOgIBXMtI/leMpLbZhi9MJnJdBBuDfcv12zwXg3LQwY0WbE
|
||||
|
||||
install:
|
||||
# Get the latest stable version of Node.js or io.js
|
||||
|
@ -12,10 +16,14 @@ install:
|
|||
- cmd: set path=%programfiles(x86)%\\Microsoft SDKs\TypeScript\3.0;%path%
|
||||
- cmd: tsc -v
|
||||
build_script:
|
||||
# - dotnet tool install --global dotnet-sonarscanner
|
||||
#- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { dotnet sonarscanner begin /k:"tidusjar_Ombi" /d:sonar.organization="tidusjar-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$env.sonarrcloud_token" /d:sonar.analysis.mode="preview" /d:sonar.github.pullRequest="$env:APPVEYOR_PULL_REQUEST_NUMBER" /d:sonar.github.repository="https://github.com/tidusjar/ombi" /d:sonar.github.oauth="$env.github_auth_token" }
|
||||
# - ps: if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) { dotnet sonarscanner begin /k:"tidusjar_Ombi" /d:sonar.organization="tidusjar-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$env.SONARRCLOUDTOKEN" }
|
||||
- ps: ./build.ps1 --settings_skipverification=true
|
||||
# - dotnet sonarscanner end /d:sonar.login="%sonarrcloudtoken%"
|
||||
|
||||
test: off
|
||||
|
||||
|
||||
after_build:
|
||||
- cmd: >-
|
||||
|
||||
|
|
|
@ -53,8 +53,6 @@ namespace Ombi.Api.Emby
|
|||
{
|
||||
username,
|
||||
pw = password,
|
||||
password = password.GetSha1Hash().ToLower(),
|
||||
passwordMd5 = password.CalcuateMd5Hash()
|
||||
};
|
||||
|
||||
request.AddJsonBody(body);
|
||||
|
@ -98,7 +96,7 @@ namespace Ombi.Api.Emby
|
|||
|
||||
request.AddQueryString("Fields", "ProviderIds,Overview");
|
||||
|
||||
request.AddQueryString("VirtualItem", "False");
|
||||
request.AddQueryString("IsVirtualItem", "False");
|
||||
|
||||
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
|
||||
}
|
||||
|
@ -149,7 +147,7 @@ namespace Ombi.Api.Emby
|
|||
request.AddQueryString("IncludeItemTypes", type);
|
||||
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
|
||||
|
||||
request.AddQueryString("VirtualItem", "False");
|
||||
request.AddQueryString("IsVirtualItem", "False");
|
||||
|
||||
AddHeaders(request, apiKey);
|
||||
|
||||
|
@ -167,7 +165,7 @@ namespace Ombi.Api.Emby
|
|||
request.AddQueryString("startIndex", startIndex.ToString());
|
||||
request.AddQueryString("limit", count.ToString());
|
||||
|
||||
request.AddQueryString("VirtualItem", "False");
|
||||
request.AddQueryString("IsVirtualItem", "False");
|
||||
|
||||
AddHeaders(request, apiKey);
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ombi.Api.Emby;
|
||||
|
@ -101,6 +102,22 @@ namespace Ombi.Core.Authentication
|
|||
return true;
|
||||
}
|
||||
|
||||
public async Task<OmbiUser> GetOmbiUserFromPlexToken(string plexToken)
|
||||
{
|
||||
var plexAccount = await _plexApi.GetAccount(plexToken);
|
||||
|
||||
// Check for a ombi user
|
||||
if (plexAccount?.user != null)
|
||||
{
|
||||
var potentialOmbiUser = await Users.FirstOrDefaultAsync(x =>
|
||||
x.ProviderUserId == plexAccount.user.id);
|
||||
return potentialOmbiUser;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sign the user into plex and make sure we can get the authentication token.
|
||||
/// <remarks>We do not check if the user is in the owners "friends" since they must have a local user account to get this far</remarks>
|
||||
|
|
|
@ -116,6 +116,7 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
|
||||
// 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())
|
||||
{
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Ombi.Core.Helpers
|
|||
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
|
||||
Results = await MovieDbApi.SearchTv(ShowInfo.name);
|
||||
foreach (TvSearchResult result in Results) {
|
||||
if (result.Name == ShowInfo.name)
|
||||
if (result.Name.Equals(ShowInfo.name, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var showIds = await MovieDbApi.GetTvExternals(result.Id);
|
||||
ShowInfo.externals.imdb = showIds.imdb_id;
|
||||
|
@ -64,14 +64,15 @@ namespace Ombi.Core.Helpers
|
|||
{
|
||||
ChildRequest = new ChildRequests
|
||||
{
|
||||
Id = model.TvDbId,
|
||||
Id = model.TvDbId, // 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 = ShowInfo.name,
|
||||
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.OrdinalIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
|
||||
ReleaseYear = FirstAir,
|
||||
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.InvariantCultureIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
|
||||
};
|
||||
|
||||
return this;
|
||||
|
|
82
src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs
Normal file
82
src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs
Normal file
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules.Request
|
||||
{
|
||||
public class ExistingPlexRequestRule : BaseRequestRule, IRules<BaseRequest>
|
||||
{
|
||||
public ExistingPlexRequestRule(IPlexContentRepository rv)
|
||||
{
|
||||
_plexContent = rv;
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plexContent;
|
||||
|
||||
/// <summary>
|
||||
/// We check if the request exists, if it does then we don't want to re-request it.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<RuleResult> Execute(BaseRequest obj)
|
||||
{
|
||||
if (obj.RequestType == RequestType.TvShow)
|
||||
{
|
||||
var tvRequest = (ChildRequests) obj;
|
||||
|
||||
var tvContent = _plexContent.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show);
|
||||
// We need to do a check on the TVDBId
|
||||
var anyTvDbMatches = await tvContent.Include(x => x.Episodes).FirstOrDefaultAsync(x => x.HasTvDb && x.TvDbId.Equals(tvRequest.Id.ToString())); // the Id on the child is the tvdbid at this point
|
||||
if (anyTvDbMatches == 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
|
||||
var titleAndYearMatch = await tvContent.Include(x=> x.Episodes).FirstOrDefaultAsync(x =>
|
||||
x.Title.Equals(tvRequest.Title, StringComparison.InvariantCultureIgnoreCase)
|
||||
&& x.ReleaseYear == tvRequest.ReleaseYear.Year.ToString());
|
||||
if (titleAndYearMatch != null)
|
||||
{
|
||||
// We have a match! Suprise Motherfucker
|
||||
return CheckExistingContent(tvRequest, titleAndYearMatch);
|
||||
}
|
||||
|
||||
// We do not have this
|
||||
return Success();
|
||||
}
|
||||
// looks like we have a match on the TVDbID
|
||||
return CheckExistingContent(tvRequest, anyTvDbMatches);
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
||||
|
||||
private RuleResult CheckExistingContent(ChildRequests child, PlexServerContent content)
|
||||
{
|
||||
foreach (var season in child.SeasonRequests)
|
||||
{
|
||||
var currentSeasonRequest =
|
||||
content.Episodes.Where(x => x.SeasonNumber == season.SeasonNumber).ToList();
|
||||
if (!currentSeasonRequest.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var e in season.Episodes)
|
||||
{
|
||||
var hasEpisode = currentSeasonRequest.Any(x => x.EpisodeNumber == e.EpisodeNumber);
|
||||
if (hasEpisode)
|
||||
{
|
||||
return Fail($"We already have episodes requested from series {child.Title}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Success();
|
||||
}
|
||||
}
|
||||
}
|
57
src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs
Normal file
57
src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules.Request
|
||||
{
|
||||
public class ExistingTvRequestRule : BaseRequestRule, IRules<BaseRequest>
|
||||
{
|
||||
public ExistingTvRequestRule(ITvRequestRepository rv)
|
||||
{
|
||||
Tv = rv;
|
||||
}
|
||||
|
||||
private ITvRequestRepository Tv { get; }
|
||||
|
||||
/// <summary>
|
||||
/// We check if the request exists, if it does then we don't want to re-request it.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<RuleResult> Execute(BaseRequest obj)
|
||||
{
|
||||
if (obj.RequestType == RequestType.TvShow)
|
||||
{
|
||||
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
|
||||
if (currentRequest == null)
|
||||
{
|
||||
return Success();
|
||||
}
|
||||
foreach (var season in tv.SeasonRequests)
|
||||
{
|
||||
var currentSeasonRequest =
|
||||
currentRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber);
|
||||
if (currentSeasonRequest == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var e in season.Episodes)
|
||||
{
|
||||
var hasEpisode = currentSeasonRequest.Episodes.Any(x => x.EpisodeNumber == e.EpisodeNumber);
|
||||
if (hasEpisode)
|
||||
{
|
||||
return Fail($"We already have episodes requested from series {tv.Title}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
|
@ -104,6 +105,21 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
{
|
||||
search.FullyAvailable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var airedButNotAvailable = search.SeasonRequests.Any(x =>
|
||||
x.Episodes.Any(c => !c.Available && c.AirDate <= DateTime.Now.Date));
|
||||
if (!airedButNotAvailable)
|
||||
{
|
||||
var unairedEpisodes = search.SeasonRequests.Any(x =>
|
||||
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date));
|
||||
if (unairedEpisodes)
|
||||
{
|
||||
search.FullyAvailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -101,13 +102,17 @@ namespace Ombi.Core.Senders
|
|||
var profiles = await _userProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId);
|
||||
if (profiles != null)
|
||||
{
|
||||
if (profiles.SonarrRootPathAnime > 0)
|
||||
{
|
||||
rootFolderPath = await RadarrRootPath(profiles.SonarrRootPathAnime, settings);
|
||||
if (profiles.RadarrRootPath > 0)
|
||||
{
|
||||
var tempPath = await RadarrRootPath(profiles.RadarrRootPath, settings);
|
||||
if (tempPath.HasValue())
|
||||
{
|
||||
rootFolderPath = tempPath;
|
||||
}
|
||||
}
|
||||
if (profiles.SonarrQualityProfileAnime > 0)
|
||||
if (profiles.RadarrQualityProfile > 0)
|
||||
{
|
||||
qualityToUse = profiles.SonarrQualityProfileAnime;
|
||||
qualityToUse = profiles.RadarrQualityProfile;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,7 +168,7 @@ namespace Ombi.Core.Senders
|
|||
{
|
||||
var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
|
||||
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
|
||||
return selectedPath.path;
|
||||
return selectedPath?.path ?? String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -355,7 +355,7 @@ namespace Ombi.Core.Senders
|
|||
var sea = new Season
|
||||
{
|
||||
seasonNumber = i,
|
||||
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0)
|
||||
monitored = false
|
||||
};
|
||||
seasonsToUpdate.Add(sea);
|
||||
}
|
||||
|
|
|
@ -82,6 +82,13 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
foreach (var ep in allEpisodes.Items)
|
||||
{
|
||||
processed++;
|
||||
|
||||
if (ep.LocationType.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
// For some reason Emby is not respecting the `IsVirtualItem` field.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let's make sure we have the parent request, stop those pesky forign key errors,
|
||||
// Damn me having data integrity
|
||||
var parent = await _repo.GetByEmbyId(ep.SeriesId);
|
||||
|
|
|
@ -174,7 +174,7 @@ namespace Ombi.Schedule.Processor
|
|||
var client = new GitHubClient(Octokit.ProductHeaderValue.Parse("OmbiV3"));
|
||||
|
||||
var releases = await client.Repository.Release.GetAll("tidusjar", "ombi");
|
||||
var latest = releases.FirstOrDefault(x => x.TagName == releaseTag);
|
||||
var latest = releases.FirstOrDefault(x => x.TagName.Equals(releaseTag, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (latest.Name.Contains("V2", CompareOptions.IgnoreCase))
|
||||
{
|
||||
latest = null;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
|
@ -22,6 +23,8 @@ namespace Ombi.Store.Entities.Requests
|
|||
[NotMapped]
|
||||
public bool ShowSubscribe { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public DateTime ReleaseYear { get; set; } // Used in the ExistingPlexRequestRule.cs
|
||||
|
||||
[ForeignKey(nameof(IssueId))]
|
||||
public List<Issues> Issues { get; set; }
|
||||
|
|
|
@ -39,7 +39,9 @@ export class RemainingRequestsComponent implements OnInit {
|
|||
public update(): void {
|
||||
const callback = (remaining => {
|
||||
this.remaining = remaining;
|
||||
this.calculateTime();
|
||||
if(this.remaining) {
|
||||
this.calculateTime();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.movie) {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()" href="#tvTab"><i class="fa fa-television"></i> {{ 'Requests.TvTab' | translate }}</a>
|
||||
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<li role="presentation" *ngIf="musicEnabled">
|
||||
<a id="albumTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectMusicTab()" href="#albumTab"><i class="fa fa-music"></i> {{ 'Requests.MusicTab' | translate }}</a>
|
||||
|
||||
</li>
|
||||
|
|
|
@ -15,6 +15,7 @@ export class RequestComponent implements OnInit {
|
|||
|
||||
public issueCategories: IIssueCategory[];
|
||||
public issuesEnabled = false;
|
||||
public musicEnabled: boolean;
|
||||
|
||||
constructor(private issuesService: IssuesService,
|
||||
private settingsService: SettingsService) {
|
||||
|
@ -23,6 +24,7 @@ export class RequestComponent implements OnInit {
|
|||
|
||||
public ngOnInit(): void {
|
||||
this.issuesService.getCategories().subscribe(x => this.issueCategories = x);
|
||||
this.settingsService.lidarrEnabled().subscribe(x => this.musicEnabled = x);
|
||||
this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
||||
|
||||
<div class="input-group">
|
||||
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
||||
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons"
|
||||
(keyup)="search($event)">
|
||||
<div class="input-group-addon right-radius">
|
||||
<div class="btn-group">
|
||||
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
|
@ -25,17 +26,18 @@
|
|||
<!-- Movie content -->
|
||||
<div id="movieList">
|
||||
<div *ngIf="searchApplied && movieResults?.length <= 0" class='no-search-results'>
|
||||
<i class='fa fa-film no-search-results-icon'></i><div class='no-search-results-text' [translate]="'Search.NoResults'"></div>
|
||||
<i class='fa fa-film no-search-results-icon'></i>
|
||||
<div class='no-search-results-text' [translate]="'Search.NoResults'"></div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let result of movieResults">
|
||||
|
||||
<div class="row" >
|
||||
|
||||
<div class="myBg backdrop" [style.background-image]="result.background"></div>
|
||||
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="myBg backdrop" [style.background-image]="result.background"></div>
|
||||
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
||||
<div class="col-sm-2 small-padding">
|
||||
<img *ngIf="result.posterPath" class="img-responsive poster" src="{{result.posterPath}}" alt="poster">
|
||||
<img *ngIf="result.posterPath" class="img-responsive poster" src="{{result.posterPath}}" alt="poster">
|
||||
|
||||
</div>
|
||||
<div class="col-sm-8 small-padding">
|
||||
|
@ -43,60 +45,80 @@
|
|||
<a href="https://www.themoviedb.org/movie/{{result.id}}/" target="_blank">
|
||||
<h4>{{result.title}} ({{result.releaseDate | amLocal | amDateFormat: 'YYYY'}})</h4>
|
||||
</a>
|
||||
<span class="tags">
|
||||
<span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | amLocal | amDateFormat: 'LL'} }}</span>
|
||||
<span *ngIf="result.digitalReleaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | amLocal | amDateFormat: 'LL'} }}</span>
|
||||
<span class="tags">
|
||||
<span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{
|
||||
'Search.TheatricalRelease' | translate: {date: result.releaseDate | amLocal |
|
||||
amDateFormat: 'LL'} }}</span>
|
||||
<span *ngIf="result.digitalReleaseDate" class="label label-info" id="releaseDateLabel"
|
||||
target="_blank">{{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate |
|
||||
amLocal | amDateFormat: 'LL'} }}</span>
|
||||
|
||||
<a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a>
|
||||
<a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span
|
||||
class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a>
|
||||
|
||||
<a *ngIf="result.trailer" href="{{result.trailer}}" id="trailerLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.Trailer'"></span></a>
|
||||
<span *ngIf="result.quality" id="qualityLabel" class="label label-success">{{result.quality}}p</span>
|
||||
|
||||
<ng-template [ngIf]="result.available"><span class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span></ng-template>
|
||||
<ng-template [ngIf]="result.approved && !result.available"><span class="label label-info" id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span></ng-template>
|
||||
<ng-template [ngIf]="result.requested && !result.approved && !result.available"><span class="label label-warning" id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span></ng-template>
|
||||
<ng-template [ngIf]="!result.requested && !result.available && !result.approved"><span class="label label-danger" id="notRequestedLabel" [translate]="'Common.NotRequested'"></span></ng-template>
|
||||
<a *ngIf="result.trailer" href="{{result.trailer}}" id="trailerLabel" target="_blank"><span
|
||||
class="label label-info" [translate]="'Search.Movies.Trailer'"></span></a>
|
||||
<span *ngIf="result.quality" id="qualityLabel" class="label label-success">{{result.quality}}p</span>
|
||||
|
||||
<ng-template [ngIf]="result.available"><span class="label label-success" id="availableLabel"
|
||||
[translate]="'Common.Available'"></span></ng-template>
|
||||
<ng-template [ngIf]="result.approved && !result.available"><span class="label label-info"
|
||||
id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span></ng-template>
|
||||
<ng-template [ngIf]="result.requested && !result.approved && !result.available"><span class="label label-warning"
|
||||
id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span></ng-template>
|
||||
<ng-template [ngIf]="!result.requested && !result.available && !result.approved"><span
|
||||
class="label label-danger" id="notRequestedLabel" [translate]="'Common.NotRequested'"></span></ng-template>
|
||||
|
||||
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<br/>
|
||||
<br />
|
||||
</div>
|
||||
<p style="font-size: 0.9rem !important">{{result.overview}}</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-2 small-padding">
|
||||
<div class="row" *ngIf="result.requested">
|
||||
<div class="col-md-2 col-md-push-10">
|
||||
|
||||
<a *ngIf="result.showSubscribe && !result.subscribed" style="color:white" (click)="subscribe(result)" pTooltip="Subscribe for notifications"> <i class="fa fa-rss"></i></a>
|
||||
<a *ngIf="result.showSubscribe && result.subscribed" style="color:red" (click)="unSubscribe(result)" pTooltip="Unsubscribe notification"> <i class="fa fa-rss"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="result.available">
|
||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> {{ 'Common.Available' | translate }}</button>
|
||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i>
|
||||
{{ 'Common.Available' | translate }}</button>
|
||||
</div>
|
||||
<div *ngIf="!result.available">
|
||||
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
|
||||
<ng-template #requestedBtn>
|
||||
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i class="fa fa-check"></i> {{ 'Common.Requested' | translate }}</button>
|
||||
</ng-template>
|
||||
<ng-template #notRequestedBtn>
|
||||
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline" (click)="request(result)">
|
||||
<i *ngIf="result.requestProcessing" class="fa fa-circle-o-notch fa-spin fa-fw"></i> <i *ngIf="!result.requestProcessing && !result.processed" class="fa fa-plus"></i>
|
||||
<i *ngIf="result.processed && !result.requestProcessing" class="fa fa-check"></i> {{ 'Common.Request' | translate }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
<button style="text-align: right" class="btn btn-sm btn-info-outline" (click)="similarMovies(result.id)"> <i class="fa fa-eye"></i> {{ 'Search.Similar' | translate }}</button>
|
||||
|
||||
<br/>
|
||||
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
|
||||
<ng-template #requestedBtn>
|
||||
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i
|
||||
class="fa fa-check"></i> {{ 'Common.Requested' | translate }}</button>
|
||||
</ng-template>
|
||||
<ng-template #notRequestedBtn>
|
||||
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline"
|
||||
(click)="request(result)">
|
||||
<i *ngIf="result.requestProcessing" class="fa fa-circle-o-notch fa-spin fa-fw"></i> <i
|
||||
*ngIf="!result.requestProcessing && !result.processed" class="fa fa-plus"></i>
|
||||
<i *ngIf="result.processed && !result.requestProcessing" class="fa fa-check"></i> {{
|
||||
'Common.Request' | translate }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="result.requested">
|
||||
<a *ngIf="result.showSubscribe && !result.subscribed" style="text-align: right" class="btn btn btn-success-outline"
|
||||
(click)="subscribe(result)" pTooltip="Subscribe for notifications when this movie becomes available">
|
||||
<i class="fa fa-rss"></i> Subscribe</a>
|
||||
<a *ngIf="result.showSubscribe && result.subscribed" style="text-align: right;" class="btn btn btn-warning-outline"
|
||||
(click)="unSubscribe(result)" pTooltip="Unsubscribe notifications when this movie becomes available">
|
||||
<i class="fa fa-rss"></i> Unsubscribe</a>
|
||||
</div>
|
||||
<button style="text-align: right" class="btn btn-sm btn-info-outline" (click)="similarMovies(result.id)">
|
||||
<i class="fa fa-eye"></i> {{ 'Search.Similar' | translate }}</button>
|
||||
|
||||
<br />
|
||||
<div *ngIf="result.available">
|
||||
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a>
|
||||
<a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Emby</a>
|
||||
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}"
|
||||
target="_blank"><i class="fa fa-eye"></i> View On Plex</a>
|
||||
<a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline"
|
||||
href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Emby</a>
|
||||
</div>
|
||||
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> Report Issue
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
@ -108,8 +130,8 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -117,4 +139,4 @@
|
|||
|
||||
|
||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequestTitle"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
|
@ -172,7 +172,7 @@ export class MovieSearchComponent implements OnInit {
|
|||
r.subscribed = true;
|
||||
this.requestService.subscribeToMovie(r.requestId)
|
||||
.subscribe(x => {
|
||||
this.notificationService.success("Subscribed To Movie!");
|
||||
this.notificationService.success(`Subscribed To Movie ${r.title}!`);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,13 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Ombi.Api;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Models;
|
||||
using Ombi.Models.External;
|
||||
using Ombi.Models.Identity;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using StackExchange.Profiling.Helpers;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
|
@ -80,7 +79,7 @@ namespace Ombi.Controllers
|
|||
{
|
||||
// Plex OAuth
|
||||
// Redirect them to Plex
|
||||
|
||||
|
||||
var websiteAddress = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}";
|
||||
//https://app.plex.tv/auth#?forwardUrl=http://google.com/&clientID=Ombi-Test&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO&pinID=798798&code=4lgfd
|
||||
var url = await _plexOAuthManager.GetOAuthUrl(model.PlexTvPin.code, websiteAddress);
|
||||
|
@ -97,6 +96,25 @@ namespace Ombi.Controllers
|
|||
return new UnauthorizedResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Token for the Ombi User if we can match the Plex user with a valid Ombi User
|
||||
/// </summary>
|
||||
[HttpPost("plextoken")]
|
||||
public async Task<IActionResult> GetTokenWithPlexToken([FromBody] PlexTokenAuthentication model)
|
||||
{
|
||||
if (!model.PlexToken.HasValue())
|
||||
{
|
||||
return BadRequest("Token was not provided");
|
||||
}
|
||||
var user = await _userManager.GetOmbiUserFromPlexToken(model.PlexToken);
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
return await CreateToken(true, user);
|
||||
}
|
||||
|
||||
|
||||
private async Task<IActionResult> CreateToken(bool rememberMe, OmbiUser user)
|
||||
{
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
|
|
7
src/Ombi/Models/External/PlexTokenAuthentication.cs
vendored
Normal file
7
src/Ombi/Models/External/PlexTokenAuthentication.cs
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ombi.Models.External
|
||||
{
|
||||
public class PlexTokenAuthentication
|
||||
{
|
||||
public string PlexToken { get; set; }
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue