Added the support for TV Series integrating with Sonarr

This commit is contained in:
Jamie Rees 2016-03-13 20:58:42 +00:00
parent 31615ff69c
commit 5dd9885885
15 changed files with 250 additions and 10 deletions

View file

@ -34,5 +34,8 @@ namespace PlexRequests.Api.Interfaces
public interface ISonarrApi public interface ISonarrApi
{ {
List<SonarrProfile> GetProfiles(string apiKey, Uri baseUrl); List<SonarrProfile> GetProfiles(string apiKey, Uri baseUrl);
SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath,
bool episodes, string apiKey, Uri baseUrl);
} }
} }

View file

@ -48,6 +48,7 @@
<Compile Include="Plex\PlexSearch.cs" /> <Compile Include="Plex\PlexSearch.cs" />
<Compile Include="Plex\PlexUserRequest.cs" /> <Compile Include="Plex\PlexUserRequest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Sonarr\SonarrAddSeries.cs" />
<Compile Include="Sonarr\SonarrProfile.cs" /> <Compile Include="Sonarr\SonarrProfile.cs" />
<Compile Include="Tv\Authentication.cs" /> <Compile Include="Tv\Authentication.cs" />
<Compile Include="Tv\TvSearchResult.cs" /> <Compile Include="Tv\TvSearchResult.cs" />

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
namespace PlexRequests.Api.Models.Sonarr
{
public class Season
{
public int seasonNumber { get; set; }
public bool monitored { get; set; }
}
public class SonarrAddSeries
{
public AddOptions addOptions { get; set; }
public string title { get; set; }
public List<Season> seasons { get; set; }
public string rootFolderPath { get; set; }
public int qualityProfileId { get; set; }
public bool seasonFolder { get; set; }
public bool monitored { get; set; }
public int tvdbId { get; set; }
public int tvRageId { get; set; }
public string cleanTitle { get; set; }
public string imdbId { get; set; }
public string titleSlug { get; set; }
public int id { get; set; }
}
public class AddOptions
{
public bool ignoreEpisodesWithFiles { get; set; }
public bool ignoreEpisodesWithoutFiles { get; set; }
public bool searchForMissingEpisodes { get; set; }
}
}

View file

@ -60,6 +60,43 @@ namespace PlexRequests.Api {
} }
} }
/// <summary>
/// Looks up a localized string similar to {
/// &quot;title&quot;: &quot;Archer (2009)&quot;,
/// &quot;seasons&quot;: [
/// {
/// &quot;seasonNumber&quot;: 5,
/// &quot;monitored&quot;: true
/// },
/// {
/// &quot;seasonNumber&quot;: 4,
/// &quot;monitored&quot;: true
/// },
/// {
/// &quot;seasonNumber&quot;: 3,
/// &quot;monitored&quot;: true
/// },
/// {
/// &quot;seasonNumber&quot;: 2,
/// &quot;monitored&quot;: true
/// },
/// {
/// &quot;seasonNumber&quot;: 1,
/// &quot;monitored&quot;: true
/// },
/// {
/// &quot;seasonNumber&quot;: 0,
/// &quot;monitored&quot;: false
/// }
/// ],
/// &quot;pat [rest of string was truncated]&quot;;.
/// </summary>
internal static string Sonarr_AddSeriesResult {
get {
return ResourceManager.GetString("Sonarr_AddSeriesResult", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to [ /// Looks up a localized string similar to [
/// { /// {

View file

@ -117,6 +117,47 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Sonarr_AddSeriesResult" xml:space="preserve">
<value>{
"title": "Archer (2009)",
"seasons": [
{
"seasonNumber": 5,
"monitored": true
},
{
"seasonNumber": 4,
"monitored": true
},
{
"seasonNumber": 3,
"monitored": true
},
{
"seasonNumber": 2,
"monitored": true
},
{
"seasonNumber": 1,
"monitored": true
},
{
"seasonNumber": 0,
"monitored": false
}
],
"path": "T:\\Archer (2009)",
"qualityProfileId": 1,
"seasonFolder": true,
"monitored": true,
"tvdbId": 110381,
"tvRageId": 23354,
"cleanTitle": "archer2009",
"imdbId": "tt1486217",
"titleSlug": "archer-2009",
"id": 1
}</value>
</data>
<data name="Sonarr_Profiles" xml:space="preserve"> <data name="Sonarr_Profiles" xml:space="preserve">
<value>[ <value>[
{ {

View file

@ -42,5 +42,13 @@ namespace PlexRequests.Api.Mocks
var obj = JsonConvert.DeserializeObject<List<SonarrProfile>>(json); var obj = JsonConvert.DeserializeObject<List<SonarrProfile>>(json);
return obj; return obj;
} }
public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, bool episodes,
string apiKey, Uri baseUrl)
{
var json = MockApiData.Sonarr_AddSeriesResult;
var obj = JsonConvert.DeserializeObject<SonarrAddSeries>(json);
return obj;
}
} }
} }

View file

@ -45,7 +45,7 @@ namespace PlexRequests.Api
public List<SonarrProfile> GetProfiles(string apiKey, Uri baseUrl) public List<SonarrProfile> GetProfiles(string apiKey, Uri baseUrl)
{ {
var request = new RestRequest { Resource = "/api/profile", Method = Method.GET}; var request = new RestRequest { Resource = "/api/profile", Method = Method.GET };
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
@ -53,5 +53,50 @@ namespace PlexRequests.Api
return obj; return obj;
} }
public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, bool episodes, string apiKey, Uri baseUrl)
{
var request = new RestRequest
{
Resource = "/api/Series?",
Method = Method.POST
};
var options = new SonarrAddSeries();
if (episodes == true)
{
options.addOptions = new AddOptions
{
ignoreEpisodesWithFiles = true,
ignoreEpisodesWithoutFiles = true,
searchForMissingEpisodes = false
};
}
else
{
options.addOptions = new AddOptions
{
ignoreEpisodesWithFiles = false,
searchForMissingEpisodes = true,
ignoreEpisodesWithoutFiles = false
};
}
options.seasonFolder = seasonFolders;
options.title = title;
options.qualityProfileId = qualityId;
options.tvdbId = tvdbId;
options.titleSlug = title;
options.seasons = new List<Season>();
options.rootFolderPath = rootPath;
request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(options);
var obj = Api.ExecuteJson<SonarrAddSeries>(request, baseUrl);
return obj;
}
} }
} }

View file

@ -37,6 +37,8 @@ namespace PlexRequests.Core.SettingModels
public int Port { get; set; } public int Port { get; set; }
public string ApiKey { get; set; } public string ApiKey { get; set; }
public string QualityProfile { get; set; } public string QualityProfile { get; set; }
public bool SeasonFolders { get; set; }
public string RootPath { get; set; }
[JsonIgnore] [JsonIgnore]
public Uri FullUri public Uri FullUri

View file

@ -58,7 +58,7 @@ namespace PlexRequests.Store
return false; return false;
} }
public string DbFile = "RequestPlex.sqlite"; public string DbFile = "PlexRequests.sqlite";
/// <summary> /// <summary>
/// Gets the database connection. /// Gets the database connection.

View file

@ -23,6 +23,7 @@ namespace PlexRequests.Store
public bool Available { get; set; } public bool Available { get; set; }
public IssueState Issues { get; set; } public IssueState Issues { get; set; }
public string OtherMessage { get; set; } public string OtherMessage { get; set; }
public bool LatestTv { get; set; }
} }
public enum RequestType public enum RequestType

View file

@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS Requested
ReleaseDate varchar(50) NOT NULL, ReleaseDate varchar(50) NOT NULL,
Status varchar(50) NOT NULL, Status varchar(50) NOT NULL,
Approved INTEGER NOT NULL, Approved INTEGER NOT NULL,
LatestTv INTEGER NOT NULL,
RequestedBy varchar(50), RequestedBy varchar(50),
RequestedDate varchar(50) NOT NULL, RequestedDate varchar(50) NOT NULL,
Available INTEGER(50), Available INTEGER(50),

View file

@ -44,13 +44,16 @@ namespace PlexRequests.UI.Modules
public class ApprovalModule : BaseModule public class ApprovalModule : BaseModule
{ {
public ApprovalModule(IRepository<RequestedModel> service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi) : base("approval") public ApprovalModule(IRepository<RequestedModel> service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi,
ISettingsService<SonarrSettings> sonarrSettings) : base("approval")
{ {
this.RequiresAuthentication(); this.RequiresAuthentication();
Service = service; Service = service;
CpService = cpService; CpService = cpService;
CpApi = cpApi; CpApi = cpApi;
SonarrApi = sonarrApi;
SonarrSettings = sonarrSettings;
Post["/approve"] = parameters => Approve((int)Request.Form.requestid); Post["/approve"] = parameters => Approve((int)Request.Form.requestid);
Post["/approveall"] = x => ApproveAll(); Post["/approveall"] = x => ApproveAll();
@ -59,7 +62,9 @@ namespace PlexRequests.UI.Modules
private IRepository<RequestedModel> Service { get; set; } private IRepository<RequestedModel> Service { get; set; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SonarrSettings> SonarrSettings { get; set; }
private ISettingsService<CouchPotatoSettings> CpService { get; } private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISonarrApi SonarrApi { get; set; }
private ICouchPotatoApi CpApi { get; } private ICouchPotatoApi CpApi { get; }
/// <summary> /// <summary>
@ -95,8 +100,21 @@ namespace PlexRequests.UI.Modules
private Response RequestTvAndUpdateStatus(RequestedModel request) private Response RequestTvAndUpdateStatus(RequestedModel request)
{ {
// TODO var sonarrSettings = SonarrSettings.GetSettings();
return Response.AsJson(new JsonResponseModel()); int qualityProfile;
int.TryParse(sonarrSettings.QualityProfile, out qualityProfile);
var result = SonarrApi.AddSeries(request.ProviderId, request.Title, qualityProfile,
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, request.LatestTv, sonarrSettings.ApiKey,
sonarrSettings.FullUri);
if (!string.IsNullOrEmpty(result.title))
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel
{
Result = false, Message = "Could not add the series to Sonarr"
});
} }
private Response RequestMovieAndUpdateStatus(RequestedModel request) private Response RequestMovieAndUpdateStatus(RequestedModel request)

View file

@ -32,6 +32,7 @@ using Nancy.Responses.Negotiation;
using NLog; using NLog;
using PlexRequests.Api; using PlexRequests.Api;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
@ -46,7 +47,7 @@ namespace PlexRequests.UI.Modules
{ {
public SearchModule(ICacheProvider cache, ISettingsService<CouchPotatoSettings> cpSettings, public SearchModule(ICacheProvider cache, ISettingsService<CouchPotatoSettings> cpSettings,
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker, ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,
IRequestService request) : base("search") IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings) : base("search")
{ {
CpService = cpSettings; CpService = cpSettings;
PrService = prSettings; PrService = prSettings;
@ -55,6 +56,8 @@ namespace PlexRequests.UI.Modules
Cache = cache; Cache = cache;
Checker = checker; Checker = checker;
RequestService = request; RequestService = request;
SonarrApi = sonarrApi;
SonarrService = sonarrSettings;
Get["/"] = parameters => RequestLoad(); Get["/"] = parameters => RequestLoad();
@ -68,11 +71,13 @@ namespace PlexRequests.UI.Modules
Post["request/tv"] = parameters => RequestTvShow((int)Request.Form.tvId, (bool)Request.Form.latest); Post["request/tv"] = parameters => RequestTvShow((int)Request.Form.tvId, (bool)Request.Form.latest);
} }
private TheMovieDbApi MovieApi { get; } private TheMovieDbApi MovieApi { get; }
private ISonarrApi SonarrApi { get; }
private TheTvDbApi TvApi { get; } private TheTvDbApi TvApi { get; }
private IRequestService RequestService { get; } private IRequestService RequestService { get; }
private ICacheProvider Cache { get; } private ICacheProvider Cache { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; } private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISettingsService<PlexRequestSettings> PrService { get; } private ISettingsService<PlexRequestSettings> PrService { get; }
private ISettingsService<SonarrSettings> SonarrService { get; }
private IAvailabilityChecker Checker { get; } private IAvailabilityChecker Checker { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private string AuthToken => Cache.GetOrSet(CacheKeys.TvDbToken, TvApi.Authenticate, 50); private string AuthToken => Cache.GetOrSet(CacheKeys.TvDbToken, TvApi.Authenticate, 50);
@ -257,10 +262,26 @@ namespace PlexRequests.UI.Modules
RequestedDate = DateTime.Now, RequestedDate = DateTime.Now,
Approved = false, Approved = false,
RequestedBy = Session[SessionKeys.UsernameKey].ToString(), RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
Issues = IssueState.None Issues = IssueState.None,
LatestTv = latest
}; };
RequestService.AddRequest(showId, model); RequestService.AddRequest(showId, model);
var settings = PrService.GetSettings();
if (!settings.RequireApproval)
{
var sonarrSettings = SonarrService.GetSettings();
int qualityProfile;
int.TryParse(sonarrSettings.QualityProfile, out qualityProfile);
var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.LatestTv, sonarrSettings.ApiKey,
sonarrSettings.FullUri);
Log.Info("Added series {0} to Sonarr, Result: {1}", model.Title, result);
Log.Trace("Model sent to Sonarr: ");
Log.Trace(model.DumpJson());
}
return Response.AsJson(new { Result = true }); return Response.AsJson(new { Result = true });
} }
private string GetAuthToken(TheTvDbApi api) private string GetAuthToken(TheTvDbApi api)

View file

@ -42,7 +42,7 @@
<div class="form-group"> <div class="form-group">
<label for="authToken" class="control-label">Plex Authorization Token</label> <label for="authToken" class="control-label">Plex Authorization Token</label>
<div class=""> <div class="">
<input type="text" class="form-control-custom form-control " id="authToken" name="PlexAuthToken" placeholder="Plex Auth Token" value="@Model.PlexAuthToken"> <input type="text" class="form-control-custom form-control" id="authToken" name="PlexAuthToken" placeholder="Plex Auth Token" value="@Model.PlexAuthToken">
</div> </div>
</div> </div>
@ -58,7 +58,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class=""> <div class="">
<button id="requestToken" class="btn btn-primary-outline-outline">Request Token <i class="fa fa-key"></i></button> <button id="requestToken" class="btn btn-primary-outline">Request Token <i class="fa fa-key"></i></button>
</div> </div>
</div> </div>

View file

@ -3,7 +3,7 @@
int port; int port;
if (Model.Port == 0) if (Model.Port == 0)
{ {
port = 80; port = 8989;
} }
else else
{ {
@ -50,6 +50,31 @@
</div> </div>
</div> </div>
<div class="form-group">
<label for="RootPath" class="control-label">Root save directory for TV shows</label>
<div>
<input type="text" class="form-control form-control-custom " id="RootPath" name="RootPath" value="@Model.RootPath">
<label>Enter the root folder where tv shows are saved. For example <strong>C:\Media\TV</strong>.</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.SeasonFolders)
{
<input type="checkbox" id="SeasonFolders" name="SeasonFolders" checked="checked">
}
else
{
<input type="checkbox" id="SeasonFolders" name="SeasonFolders">
}
<label>Enable season folders</label>
<label>Enabled Season Folders to organize seasons into individual folders within a show.</label>
</label>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button id="save" type="submit" class="btn btn-primary-outline ">Submit</button> <button id="save" type="submit" class="btn btn-primary-outline ">Submit</button>