Merge pull request #2 from tidusjar/dev

Dev
pull/83/head
Shannon Barrett 8 years ago
commit 170b2c86f0

@ -33,7 +33,8 @@ namespace PlexRequests.Api.Interfaces
{
public interface ICouchPotatoApi
{
bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl);
bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl, string profileID = default(string));
CouchPotatoStatus GetStatus(Uri url, string apiKey);
CouchPotatoProfiles GetProfiles(Uri url, string apiKey);
}
}

@ -0,0 +1,40 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ISickRageApi.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using PlexRequests.Api.Models.SickRage;
namespace PlexRequests.Api.Interfaces
{
public interface ISickRageApi
{
SickRageTvAdd AddSeries(int tvdbId, bool latest, string quality, string apiKey,
Uri baseUrl);
SickRagePing Ping(string apiKey, Uri baseUrl);
}
}

@ -49,6 +49,7 @@
<Compile Include="ICouchPotatoApi.cs" />
<Compile Include="IPlexApi.cs" />
<Compile Include="IPushbulletApi.cs" />
<Compile Include="ISickRageApi.cs" />
<Compile Include="ISonarrApi.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

@ -0,0 +1,53 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CouchPotatoProfiles.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
namespace PlexRequests.Api.Models.Movie
{
public class ProfileList
{
public bool core { get; set; }
public string _rev { get; set; }
public List<bool> finish { get; set; }
public List<string> qualities { get; set; }
public string _id { get; set; }
public string _t { get; set; }
public string label { get; set; }
public int minimum_score { get; set; }
public List<int> stop_after { get; set; }
public List<int> wait_for { get; set; }
public int order { get; set; }
public List<object> __invalid_name__3d { get; set; }
}
public class CouchPotatoProfiles
{
public List<ProfileList> list { get; set; }
public bool success { get; set; }
}
}

@ -46,6 +46,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Movie\CouchPotatoAdd.cs" />
<Compile Include="Movie\CouchPotatoProfiles.cs" />
<Compile Include="Movie\CouchPotatoStatus.cs" />
<Compile Include="Notifications\PushbulletPush.cs" />
<Compile Include="Notifications\PushbulletResponse.cs" />
@ -57,6 +58,9 @@
<Compile Include="Plex\PlexStatus.cs" />
<Compile Include="Plex\PlexUserRequest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SickRage\SickRagePing.cs" />
<Compile Include="SickRage\SickRageStatus.cs" />
<Compile Include="SickRage\SickRageTvAdd.cs" />
<Compile Include="Sonarr\SonarrAddSeries.cs" />
<Compile Include="Sonarr\SonarrProfile.cs" />
<Compile Include="Sonarr\SystemStatus.cs" />

@ -0,0 +1,40 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SickRagePing.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace PlexRequests.Api.Models.SickRage
{
public class SickRagePingData
{
public int pid { get; set; }
}
public class SickRagePing
{
public SickRagePingData data { get; set; }
public string message { get; set; }
public string result { get; set; }
}
}

@ -0,0 +1,35 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SickRageStatus.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace PlexRequests.Api.Models.SickRage
{
public static class SickRageStatus
{
public const string Wanted = "wanted";
public const string Skipped = "skipped";
public const string Ignored = "Ignored";
}
}

@ -0,0 +1,41 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SickRageTvAdd.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace PlexRequests.Api.Models.SickRage
{
public class SickRageTvAddData
{
public string name { get; set; }
}
public class SickRageTvAdd
{
public SickRageTvAddData data { get; set; }
public string message { get; set; }
public string result { get; set; }
}
}

@ -45,9 +45,17 @@ namespace PlexRequests.Api
private ApiRequest Api { get; set; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl)
public bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl, string profileId = default(string))
{
var request = new RestRequest { Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}" };
RestRequest request;
request = string.IsNullOrEmpty(profileId)
? new RestRequest {Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}"}
: new RestRequest { Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}&profile_id={profileId}" };
if (!string.IsNullOrEmpty(profileId))
{
request.AddUrlSegment("profileId", profileId);
}
request.AddUrlSegment("apikey", apiKey);
request.AddUrlSegment("imdbid", imdbid);
@ -93,5 +101,19 @@ namespace PlexRequests.Api
return Api.Execute<CouchPotatoStatus>(request,url);
}
public CouchPotatoProfiles GetProfiles(Uri url, string apiKey)
{
Log.Trace("Getting CP Profiles, ApiKey = {0}", apiKey);
var request = new RestRequest
{
Resource = "api/{apikey}/profile.list/",
Method = Method.GET
};
request.AddUrlSegment("apikey", apiKey);
return Api.Execute<CouchPotatoProfiles>(request, url);
}
}
}

@ -73,6 +73,7 @@
</Compile>
<Compile Include="Mocks\MockSonarrApi.cs" />
<Compile Include="PushbulletApi.cs" />
<Compile Include="SickrageApi.cs" />
<Compile Include="SonarrApi.cs" />
<Compile Include="CouchPotatoApi.cs" />
<Compile Include="MovieBase.cs" />

@ -0,0 +1,91 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CouchPotatoApi.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.SickRage;
using RestSharp;
namespace PlexRequests.Api
{
public class SickrageApi : ISickRageApi
{
private static Logger Log = LogManager.GetCurrentClassLogger();
public SickrageApi()
{
Api = new ApiRequest();
}
private ApiRequest Api { get; }
public SickRageTvAdd AddSeries(int tvdbId, bool latest, string quality, string apiKey,
Uri baseUrl)
{
string status;
var futureStatus = SickRageStatus.Wanted;
status = latest ? SickRageStatus.Skipped : SickRageStatus.Wanted;
var request = new RestRequest
{
Resource = "/api/{apiKey}/?cmd=show.addnew",
Method = Method.GET
};
request.AddUrlSegment("apiKey", apiKey);
request.AddQueryParameter("tvdbid", tvdbId.ToString());
request.AddQueryParameter("status", status);
request.AddQueryParameter("future_status", futureStatus);
if (!quality.Equals("default", StringComparison.CurrentCultureIgnoreCase))
{
request.AddQueryParameter("initial", quality);
}
var obj = Api.Execute<SickRageTvAdd>(request, baseUrl);
return obj;
}
public SickRagePing Ping(string apiKey, Uri baseUrl)
{
var request = new RestRequest
{
Resource = "/api/{apiKey}/?cmd=sb.ping",
Method = Method.GET
};
request.AddUrlSegment("apiKey", apiKey);
var obj = Api.ExecuteJson<SickRagePing>(request, baseUrl);
return obj;
}
}
}

@ -69,5 +69,18 @@ namespace PlexRequests.Api
return Api.Execute<TvMazeShow>(request, new Uri(Uri));
}
public TvMazeShow ShowLookupByTheTvDbId(int theTvDbId)
{
var request = new RestRequest
{
Method = Method.GET,
Resource = "lookup/shows?thetvdb={id}"
};
request.AddUrlSegment("id", theTvDbId.ToString());
request.AddHeader("Content-Type", "application/json");
return Api.Execute<TvMazeShow>(request, new Uri(Uri));
}
}
}

@ -94,6 +94,10 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>PlexRequests.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api\PlexRequests.Api.csproj">
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
<Name>PlexRequests.Api</Name>

@ -37,12 +37,19 @@ namespace PlexRequests.Core.SettingModels
public int Port { get; set; }
public string ApiKey { get; set; }
public bool Ssl { get; set; }
public string ProfileId { get; set; }
public string SubDir { get; set; }
[JsonIgnore]
public Uri FullUri
{
get
{
if (!string.IsNullOrEmpty(SubDir))
{
var formattedSubDir = Ip.ReturnUriWithSubDir(Port, Ssl, SubDir);
return formattedSubDir;
}
var formatted = Ip.ReturnUri(Port, Ssl);
return formatted;
}

@ -36,12 +36,18 @@ namespace PlexRequests.Core.SettingModels
public string Ip { get; set; }
public int Port { get; set; }
public bool Ssl { get; set; }
public string SubDir { get; set; }
[JsonIgnore]
public Uri FullUri
{
get
{
if (!string.IsNullOrEmpty(SubDir))
{
var formattedSubDir = Ip.ReturnUriWithSubDir(Port, Ssl, SubDir);
return formattedSubDir;
}
var formatted = Ip.ReturnUri(Port, Ssl);
return formatted;
}

@ -24,13 +24,36 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using Newtonsoft.Json;
using PlexRequests.Helpers;
namespace PlexRequests.Core.SettingModels
{
public class SickRageSettings : Settings
{
public bool Enabled { get; set; }
public string Ip { get; set; }
public int Port { get; set; }
public string ApiKey { get; set; }
public bool Enabled { get; set; }
public string QualityProfile { get; set; }
public bool Ssl { get; set; }
public string SubDir { get; set; }
[JsonIgnore]
public Uri FullUri
{
get
{
if (!string.IsNullOrEmpty(SubDir))
{
var formattedSubDir = Ip.ReturnUriWithSubDir(Port, Ssl, SubDir);
return formattedSubDir;
}
var formatted = Ip.ReturnUri(Port, Ssl);
return formatted;
}
}
}
}

@ -33,6 +33,7 @@ namespace PlexRequests.Core.SettingModels
{
public class SonarrSettings : Settings
{
public bool Enabled { get; set; }
public string Ip { get; set; }
public int Port { get; set; }
public string ApiKey { get; set; }
@ -40,12 +41,18 @@ namespace PlexRequests.Core.SettingModels
public bool SeasonFolders { get; set; }
public string RootPath { get; set; }
public bool Ssl { get; set; }
public string SubDir { get; set; }
[JsonIgnore]
public Uri FullUri
{
get
{
if (!string.IsNullOrEmpty(SubDir))
{
var formattedSubDir = Ip.ReturnUriWithSubDir(Port, Ssl, SubDir);
return formattedSubDir;
}
var formatted = Ip.ReturnUri(Port, Ssl);
return formatted;
}

@ -24,10 +24,13 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Data.Sqlite;
using PlexRequests.Api;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Store;
@ -70,20 +73,61 @@ namespace PlexRequests.Core
private void MigrateDb() // TODO: Remove when no longer needed
{
var result = new List<long>();
RequestedModel[] requestedModels;
var repo = new GenericRepository<RequestedModel>(Db);
var records = repo.GetAll();
var requestedModels = records as RequestedModel[] ?? records.ToArray();
if(!requestedModels.Any())
try
{
var records = repo.GetAll();
requestedModels = records as RequestedModel[] ?? records.ToArray();
}
catch (SqliteException)
{
// There is no requested table so they do not have an old version of the DB
return;
}
if (!requestedModels.Any())
{ return; }
var jsonRepo = new JsonRequestService(new RequestJsonRepository(Db, new MemoryCacheProvider()));
var result = new List<long>();
foreach (var r in requestedModels)
var api = new TvMazeApi();
foreach (var r in requestedModels.Where(x => x.Type == RequestType.TvShow))
{
var show = api.ShowLookupByTheTvDbId(r.ProviderId);
var model = new RequestedModel
{
Title = show.name,
PosterPath = show.image?.medium,
Type = RequestType.TvShow,
ProviderId = show.externals.thetvdb ?? 0,
ReleaseDate = r.ReleaseDate,
AdminNote = r.AdminNote,
Approved = r.Approved,
Available = r.Available,
ImdbId = show.externals.imdb,
Issues = r.Issues,
LatestTv = r.LatestTv,
OtherMessage = r.OtherMessage,
Overview = show.summary.RemoveHtml(),
RequestedBy = r.RequestedBy,
RequestedDate = r.ReleaseDate,
Status = show.status
};
var id = jsonRepo.AddRequest(model);
result.Add(id);
}
foreach (var source in requestedModels.Where(x => x.Type== RequestType.Movie))
{
var id = jsonRepo.AddRequest(r);
var id = jsonRepo.AddRequest(source);
result.Add(id);
}
if (result.Any(x => x == -1))
{
throw new SqliteException("Could not migrate the DB!");

@ -26,6 +26,7 @@
#endregion
using System;
using System.Linq.Expressions;
using NUnit.Framework;
namespace PlexRequests.Helpers.Tests
@ -35,7 +36,7 @@ namespace PlexRequests.Helpers.Tests
{
[TestCaseSource(nameof(UriData))]
public void CreateUri1(string uri, Uri expected)
{
{
var result = uri.ReturnUri();
Assert.That(result, Is.EqualTo(expected));
@ -58,6 +59,14 @@ namespace PlexRequests.Helpers.Tests
Assert.That(result, Is.EqualTo(expected));
}
[TestCaseSource(nameof(UriDataWithSubDir))]
public void CreateUriWithSubDir(string uri, int port, bool ssl, string subDir, Uri expected)
{
var result = uri.ReturnUriWithSubDir(port, ssl, subDir);
Assert.That(result, Is.EqualTo(expected));
}
static readonly object[] UriData =
{
new object[] { "google.com", new Uri("http://google.com/"), },
@ -84,5 +93,13 @@ namespace PlexRequests.Helpers.Tests
new object[] {"http://www.google.com/id=2", 443, new Uri("http://www.google.com:443/id=2") },
new object[] {"https://www.google.com/id=2", 443, new Uri("https://www.google.com:443/id=2") },
};
static readonly object[] UriDataWithSubDir =
{
new object[] {"www.google.com", 80, false,"test", new Uri("http://www.google.com:80/test"), },
new object[] {"www.google.com", 443, false,"test", new Uri("http://www.google.com:443/test") },
new object[] {"http://www.google.com", 443, true,"test", new Uri("https://www.google.com:443/test") },
new object[] {"https://www.google.com", 443,true,"test", new Uri("https://www.google.com:443/test") },
};
}
}

@ -33,6 +33,10 @@ namespace PlexRequests.Helpers
{
public static string RemoveHtml(this string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
var step1 = Regex.Replace(value, @"<[^>]+>|&nbsp;", "").Trim();
var step2 = Regex.Replace(step1, @"\s{2,}", " ");
return step2;

@ -53,7 +53,10 @@ namespace PlexRequests.Helpers
/// </summary>
/// <param name="val">The value.</param>
/// <param name="port">The port.</param>
/// <param name="ssl">if set to <c>true</c> [SSL].</param>
/// <param name="subdir">The subdir.</param>
/// <returns></returns>
/// <exception cref="ApplicationSettingsException">The URI is null, please check your settings to make sure you have configured the applications correctly.</exception>
/// <exception cref="System.Exception"></exception>
public static Uri ReturnUri(this string val, int port, bool ssl = default(bool))
{
@ -93,5 +96,21 @@ namespace PlexRequests.Helpers
throw new Exception(exception.Message, exception);
}
}
public static Uri ReturnUriWithSubDir(this string val, int port, bool ssl, string subDir)
{
var uriBuilder = new UriBuilder(val);
if (ssl)
{
uriBuilder.Scheme = Uri.UriSchemeHttps;
}
if (!string.IsNullOrEmpty(subDir))
{
uriBuilder.Path = subDir;
}
uriBuilder.Port = port;
return uriBuilder.Uri;
}
}
}

@ -108,7 +108,7 @@ namespace PlexRequests.Services.Tests
var requestMock = new Mock<IRequestService>();
var plexMock = new Mock<IPlexApi>();
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "wrong title", Year = "2011"} } };
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "wrong tistle", Year = "2011"} } };
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });

@ -50,7 +50,7 @@ namespace PlexRequests.Services
{
ConfigurationReader = new ConfigurationReader();
var repo = new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider());
Checker = new PlexAvailabilityChecker(new SettingsServiceV2<PlexSettings>(repo), new SettingsServiceV2<AuthenticationSettings>(repo), new RequestService(new GenericRepository<RequestedModel>(new DbConfiguration(new SqliteFactory()))), new PlexApi());
Checker = new PlexAvailabilityChecker(new SettingsServiceV2<PlexSettings>(repo), new SettingsServiceV2<AuthenticationSettings>(repo), new JsonRequestService(new RequestJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())), new PlexApi());
HostingEnvironment.RegisterObject(this);
}
@ -82,4 +82,4 @@ namespace PlexRequests.Services
{
void Start(Configuration c);
}
}
}

@ -97,11 +97,21 @@ namespace PlexRequests.Services
{
throw new ApplicationSettingsException("The settings are not configured for Plex or Authentication");
}
if (!string.IsNullOrEmpty(year))
{
var results = PlexApi.SearchContent(authSettings.PlexAuthToken, title, plexSettings.FullUri);
var result = results.Video?.FirstOrDefault(x => x.Title.Contains(title) && x.Year == year);
var directoryTitle = results.Directory?.Title == title && results.Directory?.Year == year;
return result?.Title != null || directoryTitle;
}
else
{
var results = PlexApi.SearchContent(authSettings.PlexAuthToken, title, plexSettings.FullUri);
var result = results.Video?.FirstOrDefault(x => x.Title.Contains(title));
var directoryTitle = results.Directory?.Title == title;
return result?.Title != null || directoryTitle;
}
var results = PlexApi.SearchContent(authSettings.PlexAuthToken, title, plexSettings.FullUri);
var result = results.Video?.FirstOrDefault(x => x.Title == title && x.Year == year);
var directoryTitle = results.Directory?.Title == title && results.Directory?.Year == year;
return result?.Title != null || directoryTitle;
}
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth, IEnumerable<RequestedModel> requests)

@ -74,7 +74,7 @@ namespace PlexRequests.Store.Repository
public RequestBlobs Get(int id)
{
var key = TypeName + "Get";
var key = TypeName + "Get" + id;
var item = Cache.GetOrSet(key, () =>
{
using (var con = Db.DbConnection())
@ -112,8 +112,8 @@ namespace PlexRequests.Store.Repository
public bool UpdateAll(IEnumerable<RequestBlobs> entity)
{
ResetCache();
var result = new HashSet<bool>();
using (var db = Db.DbConnection())
{
db.Open();

@ -36,24 +36,3 @@ CREATE TABLE IF NOT EXISTS Log
CallSite varchar(100) NOT NULL,
Exception varchar(100) NOT NULL
);
CREATE TABLE IF NOT EXISTS Requested
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Type INTEGER NOT NULL,
ProviderId INTEGER NOT NULL,
ImdbId varchar(50),
Overview varchar(50),
Title varchar(50) NOT NULL,
PosterPath varchar(50) NOT NULL,
ReleaseDate varchar(50) NOT NULL,
Status varchar(50) NOT NULL,
AdminNote varchar(50),
Approved INTEGER NOT NULL,
LatestTv INTEGER NOT NULL,
RequestedBy varchar(50),
RequestedDate varchar(50) NOT NULL,
Available INTEGER(50),
Issues INTEGER,
OtherMessage varchar(50)
);

@ -52,11 +52,13 @@ namespace PlexRequests.UI.Tests
private Mock<ISettingsService<AuthenticationSettings>> AuthMock { get; set; }
private Mock<ISettingsService<PlexSettings>> PlexSettingsMock { get; set; }
private Mock<ISettingsService<SonarrSettings>> SonarrSettingsMock { get; set; }
private Mock<ISettingsService<SickRageSettings>> SickRageSettingsMock { get; set; }
private Mock<ISettingsService<EmailNotificationSettings>> EmailMock { get; set; }
private Mock<ISettingsService<PushbulletNotificationSettings>> PushbulletSettings { get; set; }
private Mock<IPlexApi> PlexMock { get; set; }
private Mock<ISonarrApi> SonarrApiMock { get; set; }
private Mock<IPushbulletApi> PushbulletApi { get; set; }
private Mock<ICouchPotatoApi> CpApi { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
@ -79,6 +81,8 @@ namespace PlexRequests.UI.Tests
EmailMock = new Mock<ISettingsService<EmailNotificationSettings>>();
PushbulletApi = new Mock<IPushbulletApi>();
PushbulletSettings = new Mock<ISettingsService<PushbulletNotificationSettings>>();
CpApi = new Mock<ICouchPotatoApi>();
SickRageSettingsMock = new Mock<ISettingsService<SickRageSettings>>();
Bootstrapper = new ConfigurableBootstrapper(with =>
{
@ -93,6 +97,8 @@ namespace PlexRequests.UI.Tests
with.Dependency(EmailMock.Object);
with.Dependency(PushbulletApi.Object);
with.Dependency(PushbulletSettings.Object);
with.Dependency(CpApi.Object);
with.Dependency(SickRageSettingsMock.Object);
with.RootPathProvider<TestRootPathProvider>();
with.RequestStartup((container, pipelines, context) =>
{

@ -36,7 +36,6 @@ using Newtonsoft.Json;
using NUnit.Framework;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
@ -81,7 +80,7 @@ namespace PlexRequests.UI.Tests
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
@ -142,6 +141,7 @@ namespace PlexRequests.UI.Tests
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
var bootstrapper = new ConfigurableBootstrapper(with =>
{
@ -188,6 +188,7 @@ namespace PlexRequests.UI.Tests
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
var bootstrapper = new ConfigurableBootstrapper(with =>
{
@ -245,6 +246,7 @@ namespace PlexRequests.UI.Tests
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
var bootstrapper = new ConfigurableBootstrapper(with =>
{
@ -376,7 +378,7 @@ namespace PlexRequests.UI.Tests
with.RootPathProvider<TestRootPathProvider>();
});
bootstrapper.WithSession(new Dictionary<string, object> { {SessionKeys.UsernameKey, "abc"} });
bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
var browser = new Browser(bootstrapper);
var result = browser.Get("/userlogin/logout", with =>
@ -388,5 +390,108 @@ namespace PlexRequests.UI.Tests
Assert.That(HttpStatusCode.SeeOther, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
}
[Test]
public void LoginWithOwnerUsernameSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends()
}
};
var account = new PlexAccount { Username = "Jamie" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(account);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(new PlexAuthentication { user = new User { username = "Jamie" } });
var bootstrapper = new ConfigurableBootstrapper(with =>
{
with.Module<UserLoginModule>();
with.Dependency(AuthMock.Object);
with.Dependency(PlexMock.Object);
with.RootPathProvider<TestRootPathProvider>();
});
bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "Jamie");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("Jamie"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithOwnerUsernameAndPasswordSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends()
}
};
var plexAuth = new PlexAuthentication
{
user = new User
{
authentication_token = "abc",
username = "Jamie"
}
};
var account = new PlexAccount { Username = "Jamie" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(account);
var bootstrapper = new ConfigurableBootstrapper(with =>
{
with.Module<UserLoginModule>();
with.Dependency(AuthMock.Object);
with.Dependency(PlexMock.Object);
with.RootPathProvider<TestRootPathProvider>();
});
bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "jamie");
with.FormValue("Password", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("jamie"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
}
}

@ -62,33 +62,34 @@ namespace PlexRequests.UI
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
container.Register<IUserMapper, UserMapper>();
container.Register<ISqliteConfiguration, DbConfiguration>(new DbConfiguration(new SqliteFactory()));
container.Register<ISettingsRepository, SettingsJsonRepository>();
container.Register<ICacheProvider, MemoryCacheProvider>();
// Settings
container.Register<ISettingsService<PlexRequestSettings>, SettingsServiceV2<PlexRequestSettings>>();
container.Register<ISettingsService<CouchPotatoSettings>, SettingsServiceV2<CouchPotatoSettings>>();
container.Register<ISettingsService<AuthenticationSettings>, SettingsServiceV2<AuthenticationSettings>>();
container.Register<ISettingsService<PlexSettings>, SettingsServiceV2<PlexSettings>>();
container.Register<ISettingsService<SonarrSettings>, SettingsServiceV2<SonarrSettings>>();
container.Register<ISettingsService<SickRageSettings>, SettingsServiceV2<SickRageSettings>>();
container.Register<ISettingsService<EmailNotificationSettings>, SettingsServiceV2<EmailNotificationSettings>>();
container.Register<ISettingsService<PushbulletNotificationSettings>, SettingsServiceV2<PushbulletNotificationSettings>>();
// Repo's
container.Register<IRepository<RequestedModel>, GenericRepository<RequestedModel>>();
container.Register<IRequestService, JsonRequestService>();
container.Register<ISettingsRepository, SettingsJsonRepository>();
// Services
container.Register<IAvailabilityChecker, PlexAvailabilityChecker>();
container.Register<IConfigurationReader, ConfigurationReader>();
container.Register<IIntervals, UpdateInterval>();
// Api's
container.Register<ICouchPotatoApi, CouchPotatoApi>();
container.Register<IPushbulletApi, PushbulletApi>();
container.Register<ISickRageApi, SickrageApi>();
container.Register<ISonarrApi, SonarrApi>();
//container.Register<ISonarrApi, MockSonarrApi>();
container.Register<IPlexApi, PlexApi>();
SubscribeAllObservers(container);
@ -101,7 +102,7 @@ namespace PlexRequests.UI
TaskManager.Initialize(new PlexRegistry());
CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default);
StaticConfiguration.DisableErrorTraces = false;
base.ApplicationStartup(container, pipelines);

@ -120,3 +120,9 @@ label {
background-color: #5cb85c !important;
border-color: #5cb85c !important; }
#movieList .mix {
display: none; }
#tvList .mix {
display: none; }

@ -1 +1 @@
@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}
@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}

@ -157,3 +157,10 @@ label {
background-color: $success-colour $i;
border-color: $success-colour $i;
}
#movieList .mix{
display: none;
}
#tvList .mix{
display: none;
}

File diff suppressed because it is too large Load Diff

@ -13,6 +13,23 @@ var tvimer = 0;
movieLoad();
tvLoad();
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr('href');
if (target === "#TvShowTab") {
if (!$('#tvList').mixItUp('isLoaded')) {
$('#tvList').mixItUp({
layout: {
display: 'block'
},
load: {
filter: 'all'
}
});
}
}
});
// Approve all
$('#approveAll').click(function () {
$.ajax({
@ -210,7 +227,7 @@ $(document).on("click", ".clear", function (e) {
if (checkJsonResponse(response)) {
generateNotify("Success! Issues Cleared.", "info");
$('#issueArea').html("<div>Issue: None</div>");
$('#issueArea'+buttonId).html("<div>Issue: None</div>");
}
},
error: function (e) {
@ -274,6 +291,14 @@ function movieLoad() {
var html = searchTemplate(context);
$("#movieList").append(html);
});
$('#movieList').mixItUp({
layout: {
display: 'block'
},
load: {
filter: 'all'
}
});
});
};

@ -0,0 +1,77 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: TvSender.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.SickRage;
using PlexRequests.Api.Models.Sonarr;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Store;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Helpers
{
public class TvSender
{
public TvSender(ISonarrApi sonarrApi, ISickRageApi srApi)
{
SonarrApi = sonarrApi;
SickrageApi = srApi;
}
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickrageApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model)
{
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.Trace("Sonarr Add Result: ");
Log.Trace(result.DumpJson());
return result;
}
public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model)
{
var result = SickrageApi.AddSeries(model.ProviderId, model.LatestTv, sickRageSettings.QualityProfile,
sickRageSettings.ApiKey, sickRageSettings.FullUri);
Log.Trace("SickRage Add Result: ");
Log.Trace(result.DumpJson());
return result;
}
}
}

@ -54,11 +54,13 @@ namespace PlexRequests.UI.Modules
private ISettingsService<AuthenticationSettings> AuthService { get; }
private ISettingsService<PlexSettings> PlexService { get; }
private ISettingsService<SonarrSettings> SonarrService { get; }
private ISettingsService<SickRageSettings> SickRageService { get; }
private ISettingsService<EmailNotificationSettings> EmailService { get; }
private ISettingsService<PushbulletNotificationSettings> PushbulletService { get; }
private IPlexApi PlexApi { get; }
private ISonarrApi SonarrApi { get; }
private PushbulletApi PushbulletApi { get; }
private ICouchPotatoApi CpApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public AdminModule(ISettingsService<PlexRequestSettings> rpService,
@ -66,11 +68,13 @@ namespace PlexRequests.UI.Modules
ISettingsService<AuthenticationSettings> auth,
ISettingsService<PlexSettings> plex,
ISettingsService<SonarrSettings> sonarr,
ISettingsService<SickRageSettings> sickrage,
ISonarrApi sonarrApi,
ISettingsService<EmailNotificationSettings> email,
IPlexApi plexApi,
ISettingsService<PushbulletNotificationSettings> pbSettings,
PushbulletApi pbApi) : base("admin")
PushbulletApi pbApi,
ICouchPotatoApi cpApi) : base("admin")
{
RpService = rpService;
CpService = cpService;
@ -82,6 +86,8 @@ namespace PlexRequests.UI.Modules
PlexApi = plexApi;
PushbulletService = pbSettings;
PushbulletApi = pbApi;
CpApi = cpApi;
SickRageService = sickrage;
#if !DEBUG
this.RequiresAuthentication();
@ -106,7 +112,11 @@ namespace PlexRequests.UI.Modules
Get["/sonarr"] = _ => Sonarr();
Post["/sonarr"] = _ => SaveSonarr();
Get["/sickrage"] = _ => Sickrage();
Post["/sickrage"] = _ => SaveSickrage();
Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles();
Post["/cpprofiles"] = _ => GetCpProfiles();
Get["/emailnotification"] = _ => EmailNotifications();
Post["/emailnotification"] = _ => SaveEmailNotifications();
@ -279,7 +289,11 @@ namespace PlexRequests.UI.Modules
{
return Response.AsJson(valid.SendJsonError());
}
var sickRageEnabled = SickRageService.GetSettings().Enabled;
if (sickRageEnabled)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "SickRage is enabled, we cannot enable Sonarr and SickRage" });
}
var result = SonarrService.SaveSettings(sonarrSettings);
return Response.AsJson(result
@ -287,6 +301,35 @@ namespace PlexRequests.UI.Modules
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private Negotiator Sickrage()
{
var settings = SickRageService.GetSettings();
return View["Sickrage", settings];
}
private Response SaveSickrage()
{
var sickRageSettings = this.Bind<SickRageSettings>();
var valid = this.Validate(sickRageSettings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
var sonarrEnabled = SonarrService.GetSettings().Enabled;
if (sonarrEnabled)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sonarr is enabled, we cannot enable Sonarr and SickRage" });
}
var result = SickRageService.SaveSettings(sickRageSettings);
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for SickRage!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private Response GetSonarrQualityProfiles()
{
var settings = this.Bind<SonarrSettings>();
@ -354,5 +397,13 @@ namespace PlexRequests.UI.Modules
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushbullet Notifications!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private Response GetCpProfiles()
{
var settings = this.Bind<CouchPotatoSettings>();
var profiles = CpApi.GetProfiles(settings.FullUri, settings.ApiKey);
return Response.AsJson(profiles);
}
}
}

@ -43,7 +43,7 @@ namespace PlexRequests.UI.Modules
{
public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi,
ISettingsService<AuthenticationSettings> authSettings) : base("test")
ISettingsService<AuthenticationSettings> authSettings, ISickRageApi srApi) : base("test")
{
this.RequiresAuthentication();
@ -51,10 +51,12 @@ namespace PlexRequests.UI.Modules
SonarrApi = sonarrApi;
PlexApi = plexApi;
AuthSettings = authSettings;
SickRageApi = srApi;
Post["/cp"] = _ => CouchPotatoTest();
Post["/sonarr"] = _ => SonarrTest();
Post["/plex"] = _ => PlexTest();
Post["/sickrage"] = _ => SickRageTest();
}
@ -62,6 +64,7 @@ namespace PlexRequests.UI.Modules
private ISonarrApi SonarrApi { get; }
private ICouchPotatoApi CpApi { get; }
private IPlexApi PlexApi { get; }
private ISickRageApi SickRageApi { get; }
private ISettingsService<AuthenticationSettings> AuthSettings { get; }
private Response CouchPotatoTest()
@ -99,7 +102,7 @@ namespace PlexRequests.UI.Modules
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Sonarr, please check your settings." });
}
catch (ApplicationException e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
catch (ApplicationException e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Sonarr's status: ");
Log.Warn(e);
@ -128,7 +131,7 @@ namespace PlexRequests.UI.Modules
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Plex, please check your settings." });
}
catch (ApplicationException e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
catch (ApplicationException e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Plex's status: ");
Log.Warn(e);
@ -140,5 +143,30 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response SickRageTest()
{
var sickRageSettings = this.Bind<SickRageSettings>();
try
{
var status = SickRageApi.Ping(sickRageSettings.ApiKey, sickRageSettings.FullUri);
return status?.result == "success"
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to SickRage successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to SickRage, please check your settings." });
}
catch (ApplicationException e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get SickRage's status: ");
Log.Warn(e);
var message = $"Could not connect to SickRage, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to SickRage, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
}
}

@ -27,7 +27,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Nancy;
using Nancy.Security;
@ -36,7 +36,9 @@ using PlexRequests.Api;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Store;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
@ -45,7 +47,7 @@ namespace PlexRequests.UI.Modules
{
public ApprovalModule(IRequestService service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi,
ISettingsService<SonarrSettings> sonarrSettings) : base("approval")
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings) : base("approval")
{
this.RequiresAuthentication();
@ -54,6 +56,8 @@ namespace PlexRequests.UI.Modules
CpApi = cpApi;
SonarrApi = sonarrApi;
SonarrSettings = sonarrSettings;
SickRageApi = srApi;
SickRageSettings = srSettings;
Post["/approve"] = parameters => Approve((int)Request.Form.requestid);
Post["/approveall"] = x => ApproveAll();
@ -63,8 +67,10 @@ namespace PlexRequests.UI.Modules
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
/// <summary>
@ -74,6 +80,7 @@ namespace PlexRequests.UI.Modules
/// <returns></returns>
private Response Approve(int requestId)
{
Log.Info("approving request {0}", requestId);
if (!Context.CurrentUser.IsAuthenticated())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." });
@ -100,20 +107,63 @@ namespace PlexRequests.UI.Modules
private Response RequestTvAndUpdateStatus(RequestedModel request)
{
var sender = new TvSender(SonarrApi, SickRageApi);
var sonarrSettings = SonarrSettings.GetSettings();
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 (sonarrSettings.Enabled)
{
Log.Trace("Sending to Sonarr");
var result = sender.SendToSonarr(sonarrSettings, request);
Log.Trace("Sonarr Result: ");
Log.Trace(result.DumpJson());
if (!string.IsNullOrEmpty(result.title))
{
Log.Info("Sent successfully, Approving request now.");
request.Approved = true;
var requestResult = Service.UpdateRequest(request);
Log.Trace("Approval result: {0}",requestResult);
if (requestResult)
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Updated Sonarr but could not approve it in PlexRequests :("});
}
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not add the series to Sonarr"
});
}
if (!string.IsNullOrEmpty(result.title))
var srSettings = SickRageSettings.GetSettings();
if (srSettings.Enabled)
{
return Response.AsJson(new JsonResponseModel { Result = true });
Log.Trace("Sending to SickRage");
var result = sender.SendToSickRage(srSettings, request);
Log.Trace("SickRage Result: ");
Log.Trace(result.DumpJson());
if (result?.result == "success")
{
Log.Info("Sent successfully, Approving request now.");
request.Approved = true;
var requestResult = Service.UpdateRequest(request);
Log.Trace("Approval result: {0}", requestResult);
if (requestResult)
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Updated SickRage but could not approve it in PlexRequests :(" });
}
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = result?.message != null ? "<b>Message From SickRage: </b>" + result.message : "Could not add the series to SickRage"
});
}
return Response.AsJson(new JsonResponseModel
{
Result = false, Message = "Could not add the series to Sonarr"
Result = false,
Message = "SickRage or Sonarr are not set up!"
});
}
@ -127,7 +177,7 @@ namespace PlexRequests.UI.Modules
var cpSettings = CpService.GetSettings();
var cp = new CouchPotatoApi();
Log.Info("Adding movie to CP : {0}", request.Title);
var result = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri);
var result = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, cpSettings.ProfileId);
Log.Trace("Adding movie to CP result {0}", result);
if (result)
{
@ -184,12 +234,42 @@ namespace PlexRequests.UI.Modules
}
else
{
Log.Error("Could not approve send the movie {0} to couch potato!", r.Title);
Log.Error("Could not approve and send the movie {0} to couch potato!", r.Title);
}
}
if (r.Type == RequestType.TvShow)
{
// TODO
var sender = new TvSender(SonarrApi,SickRageApi);
var sr = SickRageSettings.GetSettings();
var sonarr = SonarrSettings.GetSettings();
if (sr.Enabled)
{
var result = sender.SendToSickRage(sr, r);
if (result?.result == "success")
{
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the TV {0} to SickRage!", r.Title);
Log.Error("SickRage Message: {0}", result?.message);
}
}
if (sonarr.Enabled)
{
var result = sender.SendToSonarr(sonarr, r);
if (result != null)
{
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the TV {0} to Sonarr!", r.Title);
}
}
}
}
try
@ -208,11 +288,10 @@ namespace PlexRequests.UI.Modules
}
private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp)
{
Log.Info("Adding movie to CP : {0}", r.Title);
var result = cp.AddMovie(r.ImdbId, settings.ApiKey, r.Title, settings.FullUri);
var result = cp.AddMovie(r.ImdbId, settings.ApiKey, r.Title, settings.FullUri, settings.ProfileId);
Log.Trace("Adding movie to CP result {0}", result);
return result;
}

@ -42,6 +42,7 @@ using PlexRequests.Helpers;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
@ -51,7 +52,7 @@ namespace PlexRequests.UI.Modules
public SearchModule(ICacheProvider cache, ISettingsService<CouchPotatoSettings> cpSettings,
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,
IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings,
ICouchPotatoApi cpApi) : base("search")
ISettingsService<SickRageSettings> sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi) : base("search")
{
CpService = cpSettings;
PrService = prSettings;
@ -63,6 +64,8 @@ namespace PlexRequests.UI.Modules
SonarrApi = sonarrApi;
SonarrService = sonarrSettings;
CouchPotatoApi = cpApi;
SickRageService = sickRageService;
SickrageApi = srApi;
Get["/"] = parameters => RequestLoad();
@ -79,11 +82,13 @@ namespace PlexRequests.UI.Modules
private ICouchPotatoApi CouchPotatoApi { get; }
private ISonarrApi SonarrApi { get; }
private TheTvDbApi TvApi { get; }
private ISickRageApi SickrageApi { get; }
private IRequestService RequestService { get; }
private ICacheProvider Cache { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISettingsService<PlexRequestSettings> PrService { get; }
private ISettingsService<SonarrSettings> SonarrService { get; }
private ISettingsService<SickRageSettings> SickRageService { get; }
private IAvailabilityChecker Checker { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private string AuthToken => Cache.GetOrSet(CacheKeys.TvDbToken, TvApi.Authenticate, 50);
@ -124,7 +129,7 @@ namespace PlexRequests.UI.Modules
// http://thetvdb.com/banners/_cache/posters/ID-1.jpg
Banner = t.show.image?.medium,
FirstAired = t.show.premiered,
Id = t.show.id,
Id = t.show.externals?.thetvdb ?? 0,
ImdbId = t.show.externals?.imdb,
Network = t.show.network?.name,
NetworkId = t.show.network?.id.ToString(),
@ -133,7 +138,7 @@ namespace PlexRequests.UI.Modules
Runtime = t.show.runtime.ToString(),
SeriesId = t.show.id,
SeriesName = t.show.name,
Status = t.show.status,
});
}
@ -186,12 +191,12 @@ namespace PlexRequests.UI.Modules
Log.Trace("Getting movie info from TheMovieDb");
Log.Trace(movieInfo.DumpJson);
//#if !DEBUG
//#if !DEBUG
if (CheckIfTitleExistsInPlex(movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString()))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{movieInfo.Title} is already in Plex!" });
}
//#endif
//#endif
var model = new RequestedModel
{
@ -215,7 +220,7 @@ namespace PlexRequests.UI.Modules
if (!settings.RequireApproval)
{
Log.Info("Adding movie to CP (No approval required)");
var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri);
var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri, cpSettings.ProfileId);
Log.Debug("Adding movie to CP result {0}", result);
if (result)
{
@ -260,21 +265,21 @@ namespace PlexRequests.UI.Modules
var tvApi = new TvMazeApi();
var showInfo = tvApi.ShowLookup(showId);
var showInfo = tvApi.ShowLookupByTheTvDbId(showId);
//#if !DEBUG
if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered.Substring(0,4))) // Take only the year Format = 2014-01-01
//#if !DEBUG
if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered?.Substring(0, 4))) // Take only the year Format = 2014-01-01
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.name} is already in Plex!" });
}
//#endif
//#endif
DateTime firstAir;
DateTime.TryParse(showInfo.premiered, out firstAir);
var model = new RequestedModel
{
ProviderId = showInfo.id,
ProviderId = showInfo.externals?.thetvdb ?? 0,
Type = RequestType.TvShow,
Overview = showInfo.summary.RemoveHtml(),
PosterPath = showInfo.image?.medium,
@ -293,20 +298,39 @@ namespace PlexRequests.UI.Modules
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);
if (result != null)
var sender = new TvSender(SonarrApi, SickrageApi);
if (sonarrSettings.Enabled)
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required)");
RequestService.AddRequest(model);
var result = sender.SendToSonarr(sonarrSettings, model);
if (result != null)
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required & Sonarr)");
RequestService.AddRequest(model);
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to Sonarr! Please check your settings." });
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." });
var srSettings = SickRageService.GetSettings();
if (srSettings.Enabled)
{
var result = sender.SendToSickRage(srSettings, model);
if (result?.result == "success")
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required & SickRage)");
RequestService.AddRequest(model);
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "<b>Message From SickRage: </b>" + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." });
}
return Response.AsJson("The request of TV Shows is not correctly set up. Please contact your admin.");
}
RequestService.AddRequest(model);
@ -314,15 +338,30 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new { Result = true });
}
private string GetTvDbAuthToken(TheTvDbApi api)
{
return Cache.GetOrSet(CacheKeys.TvDbToken, api.Authenticate, 50);
}
private bool CheckIfTitleExistsInPlex(string title, string year)
{
var result = Checker.IsAvailable(title, year);
return result;
}
private Response SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model)
{
var result = SickrageApi.AddSeries(model.ProviderId, model.LatestTv, sickRageSettings.QualityProfile,
sickRageSettings.ApiKey, sickRageSettings.FullUri);
Log.Trace("SickRage Result: ");
Log.Trace(result.DumpJson());
if (result?.result == "success")
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required & SickRage)");
RequestService.AddRequest(model);
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to SickRage! Please check your settings." });
}
}
}

@ -25,6 +25,7 @@
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
@ -101,7 +102,7 @@ namespace PlexRequests.UI.Modules
if (signedIn.user?.authentication_token != null)
{
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (CheckIfUserIsOwner(settings.PlexAuthToken, username))
if (CheckIfUserIsOwner(settings.PlexAuthToken, signedIn.user?.username))
{
Log.Debug("User is the account owner");
authenticated = true;
@ -117,6 +118,11 @@ namespace PlexRequests.UI.Modules
{
Log.Debug("Need to auth");
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
if (CheckIfUserIsOwner(settings.PlexAuthToken, username))
{
Log.Debug("User is the account owner");
authenticated = true;
}
Log.Debug("Friends list result = {0}", authenticated);
}
else if(!settings.UserAuthentication) // No auth, let them pass!
@ -152,7 +158,11 @@ namespace PlexRequests.UI.Modules
private bool CheckIfUserIsOwner(string authToken, string userName)
{
var userAccount = Api.GetAccount(authToken);
return userAccount.Username == userName;
if (userAccount == null)
{
return false;
}
return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase);
}
private bool CheckIfUserIsInPlexFriends(string username, string authToken)
@ -160,7 +170,8 @@ namespace PlexRequests.UI.Modules
var users = Api.GetUsers(authToken);
Log.Debug("Plex Users: ");
Log.Debug(users.DumpJson());
return users.User.Any(x => x.Username == username);
var allUsers = users.User?.Where(x => !string.IsNullOrEmpty(x.Username));
return allUsers != null && allUsers.Any(x => x.Username.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
private bool IsUserInDeniedList(string username, AuthenticationSettings settings)

@ -160,11 +160,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Bootstrapper.cs" />
<Compile Include="Helpers\TvSender.cs" />
<Compile Include="Helpers\ValidationHelper.cs" />
<Compile Include="Validators\PushbulletSettingsValidator.cs" />
<Compile Include="Validators\EmailNotificationSettingsValidator.cs" />
<Compile Include="Validators\CouchPotatoValidator.cs" />
<Compile Include="Validators\PlexValidator.cs" />
<Compile Include="Validators\SickRageValidator.cs" />
<Compile Include="Validators\SonarrValidator.cs" />
<Content Include="Content\bootstrap-notify.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -221,6 +223,9 @@
<Content Include="Content\jquery-2.2.1.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\jquery.mixitup.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Content\pace.css">
<DependentUpon>pace.scss</DependentUpon>
</Content>
@ -320,6 +325,9 @@
<Content Include="Views\Admin\PushbulletNotifications.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Views\Admin\Sickrage.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Web.Debug.config">
<DependentUpon>web.config</DependentUpon>
</None>

@ -48,7 +48,7 @@ namespace PlexRequests.UI
private static Logger Log = LogManager.GetCurrentClassLogger();
static void Main(string[] args)
{
var uri = string.Empty;
var port = -1;
if (args.Length > 0)
{
Log.Info("We are going to use port {0} that was passed in", args[0]);
@ -59,7 +59,7 @@ namespace PlexRequests.UI
Console.ReadLine();
Environment.Exit(1);
}
uri = $"http://*:{portResult}";
port = portResult;
}
Log.Trace("Getting product version");
@ -68,10 +68,10 @@ namespace PlexRequests.UI
var s = new Setup();
s.SetupDb();
if (string.IsNullOrEmpty(uri))
uri = GetStartupUri();
if (port == -1)
port = GetStartupPort();
var options = new StartOptions(uri)
var options = new StartOptions($"http://+:{port}")
{
ServerFactory = "Microsoft.Owin.Host.HttpListener"
};
@ -80,7 +80,7 @@ namespace PlexRequests.UI
using (WebApp.Start<Startup>(options))
{
Console.WriteLine($"Request Plex is running on {uri}");
Console.WriteLine($"Request Plex is running on the following port: {port}");
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
@ -100,19 +100,19 @@ namespace PlexRequests.UI
Console.WriteLine($"Version: {assemblyVer}");
}
private static string GetStartupUri()
private static int GetStartupPort()
{
Log.Trace("Getting startup URI");
var uri = "http://*:3579/";
Log.Trace("Getting startup Port");
var port = 3579;
var service = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var settings = service.GetSettings();
Log.Trace("Port: {0}", settings.Port);
if (settings.Port != 0)
{
uri = $"http://*:{settings.Port}";
port = settings.Port;
}
return uri;
return port;
}
private static void ConfigureTargets(string connectionString)

@ -0,0 +1,43 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SonarrValidator.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using FluentValidation;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.UI.Validators
{
public class SickRageValidator : AbstractValidator<SickRageSettings>
{
public SickRageValidator()
{
RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key.");
RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name.");
RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port.");
RuleFor(request => request.QualityProfile).NotEmpty().WithMessage("You must specify a Quality Profile.");
}
}
}

@ -51,6 +51,26 @@
</label>
</div>
</div>
<div class="form-group">
<label for="SubDir" class="control-label">CouchPotato SubDirectory</label>
<div>
<input type="text" class="form-control form-control-custom " id="SubDir" name="SubDir" value="@Model.SubDir">
</div>
</div>
<div class="form-group">
<div>
<button type="submit" id="getProfiles" class="btn btn-primary-outline">Get Quality Profiles</button>
</div>
</div>
<div class="form-group">
<label for="select" class="control-label">Quality Profiles</label>
<div id="profiles">
<select class="form-control" id="select"></select>
</div>
</div>
<br/>
<div class="form-group">
<div>
@ -75,9 +95,58 @@
<script>
$(function() {
@if (!string.IsNullOrEmpty(Model.ProfileId))
{
<text>
var qualitySelected = '@Model.ProfileId';
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
url: "cpprofiles",
dataType: "json",
success: function(response) {
response.list.forEach(function(result) {
if (result._id == qualitySelected) {
$("#select").append("<option selected='selected' value='" + result._id + "'>" + result.label + "</option>");
} else {
$("#select").append("<option value='" + result._id + "'>" + result.label + "</option>");
}
});
},
error: function(e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
</text>
}
$('#getProfiles').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
url: "cpprofiles",
dataType: "json",
success: function (response) {
response.list.forEach(function (result) {
$("#select").append("<option value='" + result._id + "'>" + result.label + "</option>");
});
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
$('#testCp').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
url: "/test/cp",
@ -107,9 +176,13 @@
return;
}
var $form = $("#mainForm");
var qualityProfile = $("#profiles option:selected").val();
var data = $form.serialize();
data = data + "&profileId=" + qualityProfile;
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
data: data,
url: $form.prop("action"),
dataType: "json",
success: function (response) {
@ -125,6 +198,6 @@
}
});
});
});
</script>

@ -43,6 +43,12 @@
</label>
</div>
</div>
<div class="form-group">
<label for="SubDir" class="control-label">Plex SubDirectory</label>
<div>
<input type="text" class="form-control form-control-custom " id="SubDir" name="SubDir" value="@Model.SubDir">
</div>
</div>
<div class="form-group">
<div>
<button id="testPlex" type="submit" class="btn btn-primary-outline">Test Connectivity</button>

@ -0,0 +1,179 @@
@Html.Partial("_Sidebar")
@{
int port;
if (Model.Port == 0)
{
port = 8081;
}
else
{
port = Model.Port;
}
}
<div class="col-sm-8 col-sm-push-1">
<form class="form-horizontal" method="POST" id="mainForm">
<fieldset>
<legend>SickRage Settings</legend>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Enabled)
{
<input type="checkbox" id="Enabled" name="Enabled" checked="checked"><text>Enabled</text>
}
else
{
<input type="checkbox" id="Enabled" name="Enabled"><text>Enabled</text>
}
</label>
</div>
</div>
<div class="form-group">
<label for="Ip" class="control-label">SickRage Hostname or IP</label>
<div class="">
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" value="@Model.Ip">
</div>
</div>
<div class="form-group">
<label for="portNumber" class="control-label">Port</label>
<div class="">
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port">
</div>
</div>
<div class="form-group">
<label for="ApiKey" class="control-label">SickRage API Key</label>
<div>
<input type="text" class="form-control form-control-custom " id="ApiKey" name="ApiKey" value="@Model.ApiKey">
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Ssl)
{
<input type="checkbox" id="Ssl" name="Ssl" checked="checked"><text>SSL</text>
}
else
{
<input type="checkbox" id="Ssl" name="Ssl"><text>SSL</text>
}
</label>
</div>
</div>
<div class="form-group">
<label for="SubDir" class="control-label">SickRage SubDirectory</label>
<div>
<input type="text" class="form-control form-control-custom " id="SubDir" name="SubDir" value="@Model.SubDir">
</div>
</div>
<div class="form-group">
<label for="profiles" class="control-label">Quality Profiles</label>
<div id="profiles">
<select class="form-control" value="selected">
<option id="default" value="default">Use Deafult</option>
<option id="sdtv" value="sdtv">SD TV</option>
<option id="sddvd" value="sddvd">SD DVD</option>
<option id="hdtv" value="hdtv">HD TV</option>
<option id="rawhdtv" value="rawhdtv">Raw HD TV</option>
<option id="hdwebdl" value="hdwebdl">HD Web DL</option>
<option id="fullhdwebdl" value="fullhdwebdl">Full HD Web DL</option>
<option id="hdbluray" value="hdbluray">HD Bluray</option>
<option id="fullhdbluray" value="fullhdbluray">Full HD Bluray</option>
</select>
</div>
</div>
<div class="form-group">
<div>
<button id="testSickRage" type="submit" class="btn btn-primary-outline">Test Connectivity</button>
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline ">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
<script>
$(function() {
@if (!string.IsNullOrEmpty(Model.QualityProfile))
{
<text>
var qualitySelected = '@Model.QualityProfile';
$('#' + qualitySelected).prop("selected", "selected");
</text>
}
$('#save').click(function (e) {
e.preventDefault();
var port = $('#portNumber').val();
if (isNaN(port)) {
generateNotify("You must specify a Port.", "warning");
return;
}
var qualityProfile = $("#profiles option:selected").val();
var $form = $("#mainForm");
var data = $form.serialize();
data = data + "&qualityProfile=" + qualityProfile;
$.ajax({
type: $form.prop("method"),
data: data,
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify("Success!", "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
$('#testSickRage').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
url: "/test/sickrage",
data: $form.serialize(),
dataType: "json",
success: function (response) {
console.log(response);
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
})
</script>

@ -14,7 +14,20 @@
<form class="form-horizontal" method="POST" id="mainForm">
<fieldset>
<legend>Sonarr Settings</legend>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Enabled)
{
<input type="checkbox" id="Enabled" name="Enabled" checked="checked"><text>Enabled</text>
}
else
{
<input type="checkbox" id="Enabled" name="Enabled"><text>Enabled</text>
}
</label>
</div>
</div>
<div class="form-group">
<label for="Ip" class="control-label">Sonarr Hostname or IP</label>
<div class="">
@ -51,6 +64,12 @@
</label>
</div>
</div>
<div class="form-group">
<label for="SubDir" class="control-label">Sonarr SubDirectory</label>
<div>
<input type="text" class="form-control form-control-custom " id="SubDir" name="SubDir" value="@Model.SubDir">
</div>
</div>
<div class="form-group">
<div>
<button type="submit" id="getProfiles" class="btn btn-primary-outline">Get Quality Profiles</button>
@ -121,7 +140,7 @@
success: function(response) {
response.forEach(function(result) {
if (result.id == qualitySelected) {
$("#select").append("<option selected='selected' value='" + result.id + "'>" + result.name + "</option>");
} else {
$("#select").append("<option value='" + result.id + "'>" + result.name + "</option>");
@ -133,11 +152,6 @@
generateNotify("Something went wrong!", "danger");
}
});
</text>
}

@ -44,7 +44,14 @@
{
<a class="list-group-item" href="/admin/sonarr">Sonarr</a>
}
@*<a class="list-group-item" href="/admin/sickbeard">Sickbeard Settings</a>*@
@if (Context.Request.Path == "/admin/sickrage")
{
<a class="list-group-item active" href="/admin/sickrage">SickRage</a>
}
else
{
<a class="list-group-item" href="/admin/sickrage">SickRage</a>
}
@if (Context.Request.Path == "/admin/emailnotification")
{

@ -22,13 +22,38 @@
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-content contentList">
<div class="btn-group col-sm-push-10">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Filter
<i class="fa fa-filter"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#" class="filter" data-filter="all">All</a></li>
<li><a href="#" class="filter" data-filter=".approved-true">Approved</a></li>
<li><a href="#" class="filter" data-filter=".approved-false">Not Approved</a></li>
<li><a href="#" class="filter" data-filter=".available-true">Available</a></li>
<li><a href="#" class="filter" data-filter=".available-false">Not Available</a></li>
</ul>
</div>
<div class="btn-group col-sm-push-10">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Order
<i class="fa fa-sort"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#" class="sort" data-sort="default">Default</a></li>
<li><a href="#" class="sort" data-sort="requestorder:asc">Requested Date</a></li>
</ul>
</div>
@if (Model.SearchForMovies)
{
<!-- Movie tab -->
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<br />
<br />
<br/>
<br/>
<!-- Movie content -->
<div id="movieList">
</div>
@ -37,10 +62,11 @@
@if (Model.SearchForTvShows)
{
<!-- TV tab -->
<!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<br />
<br />
<br/>
<br/>
<!-- TV content -->
<div id="tvList">
</div>
@ -52,7 +78,7 @@
<script id="search-template" type="text/x-handlebars-template">
<div id="{{requestId}}Template">
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}}">
<div class="row">
<div class="col-sm-2">
{{#if_eq type "movie"}}
@ -95,7 +121,7 @@
</div>
<div>Requested By: {{requestedBy}}</div>
<div>Requested Date: {{requestedDate}}</div>
<div id="issueArea">
<div id="issueArea{{requestId}}">
{{#if otherMessage}}
<div>Message: {{otherMessage}}</div>
{{else}}
@ -118,7 +144,7 @@
{{/if_eq}}
<form method="POST" action="/requests/delete" id="delete{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-plus"></i> Remove</button>
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> Remove</button>
</form>
<form method="POST" action="/requests/clearissues" id="clear{{requestId}}">
@ -129,9 +155,9 @@
<form method="POST" action="/requests/changeavailability" id="change{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
{{#if_eq available true}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit">Mark Unavailable</button>
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> Mark Unavailable</button>
{{else}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit">Mark Available</button>
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> Mark Available</button>
{{/if_eq}}
</form>
@ -150,16 +176,15 @@
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li>
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li>
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li>
{{#if_eq admin true}}
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
{{#if_eq admin true}}
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
{{/if_eq}}
</ul>
</div>
</form>
</div>
@* // TODO add Issues to the view *@
</div>
<hr />
</div>
@ -208,3 +233,5 @@
</div>
<script src="/Content/requests.js" type="text/javascript"></script>

@ -20,6 +20,7 @@
<script src="/Content/bootstrap-notify.min.js"></script>
<script src="/Content/site.js"></script>
<script src="/Content/pace.min.js"></script>
<script src="/Content/jquery.mixitup.js"></script>
</head>
<body>

@ -21,7 +21,7 @@ I wanted to write a similar application in .Net!
#Preview
![Preview](http://i.imgur.com/yNztwTn.gif)
![Preview](http://i.imgur.com/ucCFUvd.gif)
#Installation
Download the latest [Release](https://github.com/tidusjar/PlexRequests.Net/releases).
@ -33,6 +33,8 @@ Just run `PlexRequests.exe`! (Mono compatible `mono PlexRequests.exe`)
To configure PlexRequests you need to register an admin user by clicking on Admin (top right) and press the Register link.
You will then have a admin menu option once registered where you can setup Sonarr, Couchpotato and any other settings.
Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile:
# Contributors
We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it!
@ -40,7 +42,11 @@ We are looking for any contributions to the project! Just pick up a task, if you
Please feed free to submit a pull request!
# Donation
If you feel like donating you can [here!](paypal.me/PlexRequestsNet)
If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet)
###### A massive thanks to everyone below!
[heartisall](https://github.com/heartisall)
# Sponsors

@ -1,11 +1,11 @@
version: 1.3.{build}
version: 1.4.{build}
configuration: Release
assembly_info:
patch: true
file: '**\AssemblyInfo.*'
assembly_version: '1.3.0'
assembly_version: '1.4.1'
assembly_file_version: '{version}'
assembly_informational_version: '1.3.0'
assembly_informational_version: '1.4.1'
before_build:
- cmd: appveyor-retry nuget restore
build:
@ -18,7 +18,7 @@ after_build:
deploy:
- provider: GitHub
release: PlexRequests $(appveyor_build_version)
release: PlexRequests v$(appveyor_build_version)
auth_token:
secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp
draft: true

Loading…
Cancel
Save