Added the support for TV Series integrating with Sonarr

pull/13/head
Jamie Rees 8 years ago
parent 31615ff69c
commit 5dd9885885

@ -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);
} }
} }

@ -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" />

@ -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; }
}
}

@ -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 [
/// { /// {

@ -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>[
{ {

@ -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;
}
} }
} }

@ -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;
}
} }
} }

@ -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

@ -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.

@ -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

@ -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),

@ -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)

@ -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)

@ -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>

@ -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>

Loading…
Cancel
Save