Merge pull request #42 from tidusjar/dev

Release 1.4.0
pull/52/head v1.4.0
Jamie 8 years ago
commit 14d4f6f098

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

@ -37,6 +37,6 @@ namespace PlexRequests.Api.Interfaces
PlexFriends GetUsers(string authToken);
PlexSearch SearchContent(string authToken, string searchTerm, Uri plexFullHost);
PlexStatus GetStatus(string authToken, Uri uri);
PlexAccount GetAccount(string authToken);
}
}

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

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace PlexRequests.Api.Models.Plex
{
[XmlRoot(ElementName = "user")]
public class PlexAccount
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "username")]
public string Username { get; set; }
[XmlAttribute(AttributeName = "email")]
public string Email { get; set; }
[XmlAttribute(AttributeName = "authenticationToken")]
public string AuthToken { get; set; }
}
}

@ -238,9 +238,79 @@ namespace PlexRequests.Api.Models.Plex
public string Type { get; set; }
}
[XmlRoot(ElementName = "Directory")]
public class Directory1
{
[XmlElement(ElementName = "Genre")]
public List<Genre> Genre { get; set; }
[XmlElement(ElementName = "Role")]
public List<Role> Role { get; set; }
[XmlAttribute(AttributeName = "allowSync")]
public string AllowSync { get; set; }
[XmlAttribute(AttributeName = "librarySectionID")]
public string LibrarySectionID { get; set; }
[XmlAttribute(AttributeName = "librarySectionTitle")]
public string LibrarySectionTitle { get; set; }
[XmlAttribute(AttributeName = "librarySectionUUID")]
public string LibrarySectionUUID { get; set; }
[XmlAttribute(AttributeName = "personal")]
public string Personal { get; set; }
[XmlAttribute(AttributeName = "sourceTitle")]
public string SourceTitle { get; set; }
[XmlAttribute(AttributeName = "ratingKey")]
public string RatingKey { get; set; }
[XmlAttribute(AttributeName = "key")]
public string Key { get; set; }
[XmlAttribute(AttributeName = "studio")]
public string Studio { get; set; }
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(AttributeName = "title")]
public string Title { get; set; }
[XmlAttribute(AttributeName = "contentRating")]
public string ContentRating { get; set; }
[XmlAttribute(AttributeName = "summary")]
public string Summary { get; set; }
[XmlAttribute(AttributeName = "index")]
public string Index { get; set; }
[XmlAttribute(AttributeName = "rating")]
public string Rating { get; set; }
[XmlAttribute(AttributeName = "viewCount")]
public string ViewCount { get; set; }
[XmlAttribute(AttributeName = "lastViewedAt")]
public string LastViewedAt { get; set; }
[XmlAttribute(AttributeName = "year")]
public string Year { get; set; }
[XmlAttribute(AttributeName = "thumb")]
public string Thumb { get; set; }
[XmlAttribute(AttributeName = "art")]
public string Art { get; set; }
[XmlAttribute(AttributeName = "banner")]
public string Banner { get; set; }
[XmlAttribute(AttributeName = "theme")]
public string Theme { get; set; }
[XmlAttribute(AttributeName = "duration")]
public string Duration { get; set; }
[XmlAttribute(AttributeName = "originallyAvailableAt")]
public string OriginallyAvailableAt { get; set; }
[XmlAttribute(AttributeName = "leafCount")]
public string LeafCount { get; set; }
[XmlAttribute(AttributeName = "viewedLeafCount")]
public string ViewedLeafCount { get; set; }
[XmlAttribute(AttributeName = "childCount")]
public string ChildCount { get; set; }
[XmlAttribute(AttributeName = "addedAt")]
public string AddedAt { get; set; }
[XmlAttribute(AttributeName = "updatedAt")]
public string UpdatedAt { get; set; }
}
[XmlRoot(ElementName = "MediaContainer")]
public class PlexSearch
{
[XmlElement(ElementName = "Directory")]
public Directory1 Directory { get; set; }
[XmlElement(ElementName = "Video")]
public List<Video> Video { get; set; }
[XmlElement(ElementName = "Provider")]

@ -46,9 +46,11 @@
</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" />
<Compile Include="Plex\PlexAccount.cs" />
<Compile Include="Plex\PlexAuthentication.cs" />
<Compile Include="Plex\PlexError.cs" />
<Compile Include="Plex\PlexFriends.cs" />
@ -56,10 +58,15 @@
<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" />
<Compile Include="Tv\Authentication.cs" />
<Compile Include="Tv\TvMazeSearch.cs" />
<Compile Include="Tv\TVMazeShow.cs" />
<Compile Include="Tv\TvSearchResult.cs" />
<Compile Include="Tv\TvShow.cs" />
<Compile Include="Tv\TvShowImages.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; }
}
}

@ -1,7 +1,7 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SettingsModel.cs
// File: SickRageStatus.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
@ -24,15 +24,12 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Dapper.Contrib.Extensions;
namespace PlexRequests.Store
namespace PlexRequests.Api.Models.SickRage
{
[Table("Settings")]
public class SettingsModel : Entity
public static class SickRageStatus
{
public int Port { get; set; }
public bool UserAuthentication { get; set; }
public string PlexAuthToken { get; set; }
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; }
}
}

@ -0,0 +1,27 @@
using System.Collections.Generic;
namespace PlexRequests.Api.Models.Tv
{
public class TvMazeShow
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public string type { get; set; }
public string language { get; set; }
public List<string> genres { get; set; }
public string status { get; set; }
public int runtime { get; set; }
public string premiered { get; set; }
public Schedule schedule { get; set; }
public Rating rating { get; set; }
public int weight { get; set; }
public Network network { get; set; }
public object webChannel { get; set; }
public Externals externals { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public int updated { get; set; }
public Links _links { get; set; }
}
}

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlexRequests.Api.Models.Tv
{
public class Schedule
{
public string time { get; set; }
public List<object> days { get; set; }
}
public class Rating
{
public double? average { get; set; }
}
public class Country
{
public string name { get; set; }
public string code { get; set; }
public string timezone { get; set; }
}
public class Network
{
public int id { get; set; }
public string name { get; set; }
public Country country { get; set; }
}
public class Externals
{
public int? tvrage { get; set; }
public int? thetvdb { get; set; }
public string imdb { get; set; }
}
public class Image
{
public string medium { get; set; }
public string original { get; set; }
}
public class Self
{
public string href { get; set; }
}
public class Previousepisode
{
public string href { get; set; }
}
public class Nextepisode
{
public string href { get; set; }
}
public class Links
{
public Self self { get; set; }
public Previousepisode previousepisode { get; set; }
public Nextepisode nextepisode { get; set; }
}
public class Show
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public string type { get; set; }
public string language { get; set; }
public List<object> genres { get; set; }
public string status { get; set; }
public int? runtime { get; set; }
public string premiered { get; set; }
public Schedule schedule { get; set; }
public Rating rating { get; set; }
public int weight { get; set; }
public Network network { get; set; }
public object webChannel { get; set; }
public Externals externals { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public int updated { get; set; }
public Links _links { get; set; }
}
public class TvMazeSearch
{
public double score { get; set; }
public Show show { get; set; }
}
}

@ -98,13 +98,13 @@ namespace PlexRequests.Api
try
{
var json = JsonConvert.DeserializeObject<T>(response.Content);
return json;
}
catch (Exception e)
{
Log.Fatal(e);
Log.Info(response.Content);
throw;
}
}

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

@ -134,6 +134,25 @@ namespace PlexRequests.Api
return users;
}
public PlexAccount GetAccount(string authToken)
{
var request = new RestRequest
{
Method = Method.GET,
};
request.AddHeader("X-Plex-Client-Identifier", "Test213");
request.AddHeader("X-Plex-Product", "Request Plex");
request.AddHeader("X-Plex-Version", Version);
request.AddHeader("X-Plex-Token", authToken);
request.AddHeader("Content-Type", "application/xml");
var api = new ApiRequest();
var account = api.ExecuteXml<PlexAccount>(request, new Uri("https://plex.tv/users/account"));
return account;
}
}
}

@ -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" />
@ -81,6 +82,8 @@
<Compile Include="TvBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TheTvDbApi.cs" />
<Compile Include="TvMazeApi.cs" />
<Compile Include="TvMazeBase.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

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

@ -0,0 +1,86 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: TvMazeApi.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 System.Collections.Generic;
using NLog;
using PlexRequests.Api.Models.Tv;
using RestSharp;
namespace PlexRequests.Api
{
public class TvMazeApi : TvMazeBase
{
public TvMazeApi()
{
Api = new ApiRequest();
}
private ApiRequest Api { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public List<TvMazeSearch> Search(string searchTerm)
{
var request = new RestRequest
{
Method = Method.GET,
Resource = "search/shows?q={searchTerm}"
};
request.AddUrlSegment("searchTerm", searchTerm);
request.AddHeader("Content-Type", "application/json");
return Api.Execute<List<TvMazeSearch>>(request, new Uri(Uri));
}
public TvMazeShow ShowLookup(int showId)
{
var request = new RestRequest
{
Method = Method.GET,
Resource = "shows/{id}"
};
request.AddUrlSegment("id", showId.ToString());
request.AddHeader("Content-Type", "application/json");
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));
}
}
}

@ -0,0 +1,33 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: TvMazeBase.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
{
public class TvMazeBase
{
protected string Uri = "http://api.tvmaze.com";
}
}

@ -34,6 +34,7 @@ namespace PlexRequests.Core.Tests
public class StatusCheckerTests
{
[Test]
[Ignore("API Limit")]
public void CheckStatusTest()
{
var checker = new StatusChecker();
@ -42,4 +43,4 @@ namespace PlexRequests.Core.Tests
Assert.That(status, Is.Not.Null);
}
}
}
}

@ -32,10 +32,10 @@ namespace PlexRequests.Core
{
public interface IRequestService
{
long AddRequest(int providerId, RequestedModel model);
long AddRequest(RequestedModel model);
bool CheckRequest(int providerId);
void DeleteRequest(int tmdbId);
void UpdateRequest(RequestedModel model);
void DeleteRequest(RequestedModel request);
bool UpdateRequest(RequestedModel model);
RequestedModel Get(int id);
IEnumerable<RequestedModel> GetAll();
bool BatchUpdate(List<RequestedModel> model);

@ -42,11 +42,18 @@ namespace PlexRequests.Core
Repo = repo;
}
private IRequestRepository Repo { get; }
public long AddRequest(int providerId, RequestedModel model)
public long AddRequest(RequestedModel model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ReturnBytes(model), ProviderId = model.ProviderId};
var entity = new RequestBlobs { Type = model.Type, Content = ReturnBytes(model), ProviderId = model.ProviderId };
var id = Repo.Insert(entity);
return Repo.Insert(entity);
// TODO Keep an eye on this, since we are now doing 2 DB update for 1 single request, inserting and then updating
model.Id = (int)id;
entity = new RequestBlobs { Type = model.Type, Content = ReturnBytes(model), ProviderId = model.ProviderId, Id = (int)id };
var result = Repo.Update(entity);
return result ? id : -1;
}
public bool CheckRequest(int providerId)
@ -55,16 +62,16 @@ namespace PlexRequests.Core
return blobs.Any(x => x.ProviderId == providerId);
}
public void DeleteRequest(int tmdbId)
public void DeleteRequest(RequestedModel request)
{
var blob = Repo.GetAll().FirstOrDefault(x => x.ProviderId == tmdbId);
var blob = Repo.Get(request.Id);
Repo.Delete(blob);
}
public void UpdateRequest(RequestedModel model)
public bool UpdateRequest(RequestedModel model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ReturnBytes(model), ProviderId = model.ProviderId, Id = model.Id};
Repo.Update(entity);
var entity = new RequestBlobs { Type = model.Type, Content = ReturnBytes(model), ProviderId = model.ProviderId, Id = model.Id };
return Repo.Update(entity);
}
public RequestedModel Get(int id)
@ -85,7 +92,7 @@ namespace PlexRequests.Core
public bool BatchUpdate(List<RequestedModel> model)
{
var entities = model.Select(m => new RequestBlobs { Type = m.Type, Content = ReturnBytes(m), ProviderId = m.ProviderId }).ToList();
var entities = model.Select(m => new RequestBlobs { Type = m.Type, Content = ReturnBytes(m), ProviderId = m.ProviderId, Id = m.Id }).ToList();
return Repo.UpdateAll(entities);
}

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

@ -40,7 +40,7 @@ namespace PlexRequests.Core
private IRepository<RequestedModel> Repo { get; set; }
public long AddRequest(int providerId, RequestedModel model)
public long AddRequest(RequestedModel model)
{
return Repo.Insert(model);
}
@ -50,16 +50,15 @@ namespace PlexRequests.Core
return Repo.GetAll().Any(x => x.ProviderId == providerId);
}
public void DeleteRequest(int tmdbId)
public void DeleteRequest(RequestedModel model)
{
var entity = Repo.GetAll().FirstOrDefault(x => x.ProviderId == tmdbId);
var entity = Repo.Get(model.Id);
Repo.Delete(entity);
}
public void UpdateRequest(RequestedModel model)
public bool UpdateRequest(RequestedModel model)
{
Repo.Update(model);
return Repo.Update(model);
}
/// <summary>

@ -36,13 +36,15 @@ namespace PlexRequests.Core.SettingModels
public string Ip { get; set; }
public int Port { get; set; }
public string ApiKey { get; set; }
public bool Ssl { get; set; }
public string ProfileId { get; set; }
[JsonIgnore]
public Uri FullUri
{
get
{
var formatted = Ip.ReturnUri(Port);
var formatted = Ip.ReturnUri(Port, Ssl);
return formatted;
}
}

@ -24,13 +24,30 @@
// 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; }
[JsonIgnore]
public Uri FullUri
{
get
{
var formatted = Ip.ReturnUri(Port, Ssl);
return formatted;
}
}
}
}

@ -33,19 +33,21 @@ 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; }
public string QualityProfile { get; set; }
public bool SeasonFolders { get; set; }
public string RootPath { get; set; }
public bool Ssl { get; set; }
[JsonIgnore]
public Uri FullUri
{
get
{
var formatted = Ip.ReturnUri(Port);
var formatted = Ip.ReturnUri(Port, Ssl);
return formatted;
}
}

@ -24,7 +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;
@ -46,6 +52,7 @@ namespace PlexRequests.Core
CreateDefaultSettingsPage();
}
MigrateDb();
return Db.DbConnection().ConnectionString;
}
@ -63,5 +70,82 @@ namespace PlexRequests.Core
var s = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
s.SaveSettings(defaultSettings);
}
private void MigrateDb() // TODO: Remove when no longer needed
{
var result = new List<long>();
RequestedModel[] requestedModels;
var repo = new GenericRepository<RequestedModel>(Db);
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 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(source);
result.Add(id);
}
if (result.Any(x => x == -1))
{
throw new SqliteException("Could not migrate the DB!");
}
if (result.Count != requestedModels.Length)
{
throw new SqliteException("Could not migrate the DB! count is different");
}
// Now delete the old requests
foreach (var oldRequest in requestedModels)
{
repo.Delete(oldRequest);
}
}
}
}

@ -0,0 +1,56 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: HtmlRemoverTests.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 NUnit.Framework;
namespace PlexRequests.Helpers.Tests
{
[TestFixture]
public class HtmlRemoverTests
{
[Test]
public void RemoveHtmlBasic()
{
var html = "this is <b>bold</b> <p>para</p> OK!";
var result = html.RemoveHtml();
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EqualTo("this is bold para OK!"));
}
[Test]
public void RemoveHtmlMoreTags()
{
// Good 'ol Ali G ;)
var html = "<p><strong><em>\"Ali G: Rezurection\"</em></strong> includes every episode of <em>Da Ali G Show</em> with new, original introductions by star, creator/writer Sacha Baron Cohen, along with the BAFTA(R) Award-winning English episodes of <em>Da Ali G Show</em> which have never aired on American television and <em>The Best of Ali G</em>.</p>";
var result = html.RemoveHtml();
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EqualTo("\"Ali G: Rezurection\" includes every episode of Da Ali G Show with new, original introductions by star, creator/writer Sacha Baron Cohen, along with the BAFTA(R) Award-winning English episodes of Da Ali G Show which have never aired on American television and The Best of Ali G."));
}
}
}

@ -70,6 +70,7 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="HtmlRemoverTests.cs" />
<Compile Include="AssemblyHelperTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UriHelperTests.cs" />

@ -0,0 +1,45 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: HtmlRemover.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.Text.RegularExpressions;
namespace PlexRequests.Helpers
{
public static class HtmlRemover
{
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;
}
}
}

@ -48,6 +48,7 @@
<ItemGroup>
<Compile Include="AssemblyHelper.cs" />
<Compile Include="Exceptions\ApplicationSettingsException.cs" />
<Compile Include="HtmlRemover.cs" />
<Compile Include="ICacheProvider.cs" />
<Compile Include="LoggingHelper.cs" />
<Compile Include="MemoryCacheProvider.cs" />

@ -55,7 +55,7 @@ namespace PlexRequests.Services.Tests
var plexMock = new Mock<IPlexApi>();
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
Assert.Throws<ApplicationSettingsException>(() => Checker.IsAvailable("title"), "We should be throwing an exception since we cannot talk to the services.");
Assert.Throws<ApplicationSettingsException>(() => Checker.IsAvailable("title", "2013"), "We should be throwing an exception since we cannot talk to the services.");
}
[Test]
@ -66,7 +66,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 = "title" } } };
var searchResult = new PlexSearch {Video = new List<Video> {new Video {Title = "title", Year = "2011"} } };
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
@ -74,7 +74,28 @@ namespace PlexRequests.Services.Tests
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
var result = Checker.IsAvailable("title");
var result = Checker.IsAvailable("title", "2011");
Assert.That(result, Is.True);
}
[Test]
public void IsAvailableDirectoryTitleTest()
{
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
var requestMock = new Mock<IRequestService>();
var plexMock = new Mock<IPlexApi>();
var searchResult = new PlexSearch { Directory = new Directory1 {Title = "title", Year = "2013"} };
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(searchResult);
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
var result = Checker.IsAvailable("title", "2013");
Assert.That(result, Is.True);
}
@ -87,7 +108,28 @@ 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" } } };
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" });
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(searchResult);
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
var result = Checker.IsAvailable("title", "2011");
Assert.That(result, Is.False);
}
[Test]
public void IsYearDoesNotMatchTest()
{
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
var requestMock = new Mock<IRequestService>();
var plexMock = new Mock<IPlexApi>();
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "title", Year = "2019" } } };
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
@ -95,7 +137,7 @@ namespace PlexRequests.Services.Tests
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
var result = Checker.IsAvailable("title");
var result = Checker.IsAvailable("title", "2011");
Assert.That(result, Is.False);
}

@ -29,6 +29,6 @@ namespace PlexRequests.Services.Interfaces
public interface IAvailabilityChecker
{
void CheckAndUpdateAll(long check);
bool IsAvailable(string title);
bool IsAvailable(string title, string year);
}
}

@ -85,9 +85,10 @@ namespace PlexRequests.Services
/// Determines whether the specified search term is available.
/// </summary>
/// <param name="title">The search term.</param>
/// <param name="year">The year.</param>
/// <returns></returns>
/// <exception cref="ApplicationSettingsException">The settings are not configured for Plex or Authentication</exception>
public bool IsAvailable(string title)
public bool IsAvailable(string title, string year)
{
var plexSettings = Plex.GetSettings();
var authSettings = Auth.GetSettings();
@ -96,10 +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);
return result?.Title != null;
}
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth, IEnumerable<RequestedModel> requests)

@ -36,7 +36,7 @@
<Private>True</Private>
</Reference>
<Reference Include="Dapper.Contrib, Version=1.40.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.Contrib.1.42\lib\net45\Dapper.Contrib.dll</HintPath>
<HintPath>..\packages\Dapper.Contrib.1.43\lib\net45\Dapper.Contrib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Data.Sqlite">
@ -68,7 +68,6 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Repository\SettingsJsonRepository.cs" />
<Compile Include="Repository\RequestJsonRepository.cs" />
<Compile Include="SettingsModel.cs" />
<Compile Include="GenericRepository.cs" />
<Compile Include="RequestedModel.cs" />
<Compile Include="UserRepository.cs" />

@ -53,7 +53,8 @@ namespace PlexRequests.Store.Repository
ResetCache();
using (var con = Db.DbConnection())
{
return con.Insert(entity);
var id = con.Insert(entity);
return id;
}
}
@ -73,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())
@ -111,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();

@ -8,36 +8,6 @@ CREATE TABLE IF NOT EXISTS User
Password varchar(100) NOT NULL
);
CREATE TABLE IF NOT EXISTS Settings
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Port INTEGER NOT NULL,
UserAuthentication INTEGER NOT NULL,
PlexAuthToken varchar(50)
);
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)
);
CREATE TABLE IF NOT EXISTS GlobalSettings
(
@ -65,4 +35,4 @@ CREATE TABLE IF NOT EXISTS Log
Message varchar(100) NOT NULL,
CallSite varchar(100) NOT NULL,
Exception varchar(100) NOT NULL
);
);

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Dapper" version="1.42" targetFramework="net452" />
<package id="Dapper.Contrib" version="1.42" targetFramework="net452" />
<package id="Dapper.Contrib" version="1.43" targetFramework="net46" />
<package id="NLog" version="4.2.3" targetFramework="net452" />
</packages>

@ -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, RequestService>();
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);

@ -108,7 +108,7 @@ $(".theNoteSaveButton").click(function (e) {
if (checkJsonResponse(response)) {
generateNotify("Success! Added Note.", "success");
$("#myModal").modal("hide");
$('#adminNotesArea').html("<div>Note from Admin: " + comment + "</div>");
$('#adminNotesArea' + e.target.value).html("<div>Note from Admin: " + comment + "</div>");
}
},
error: function (e) {
@ -210,7 +210,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) {

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

@ -48,8 +48,6 @@ namespace PlexRequests.UI.Models
public string AirsTime { get; set; }
public string Rating { get; set; }
public string ImdbId { get; set; }
public string Zap2ItId { get; set; }
public string Added { get; set; }
public int SiteRating { get; set; }
}
}

@ -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
@ -44,8 +46,8 @@ namespace PlexRequests.UI.Modules
public class ApprovalModule : BaseModule
{
public ApprovalModule(IRepository<RequestedModel> service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi,
ISettingsService<SonarrSettings> sonarrSettings) : base("approval")
public ApprovalModule(IRequestService service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi,
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings) : base("approval")
{
this.RequiresAuthentication();
@ -54,17 +56,21 @@ 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();
}
private IRepository<RequestedModel> Service { get; set; }
private IRequestService Service { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SonarrSettings> SonarrSettings { get; set; }
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISonarrApi SonarrApi { get; set; }
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)
{
@ -135,7 +185,7 @@ namespace PlexRequests.UI.Modules
request.Approved = true;
// Update the record
var inserted = Service.Update(request);
var inserted = Service.UpdateRequest(request);
return Response.AsJson(inserted
? new JsonResponseModel {Result = true}
@ -184,18 +234,48 @@ 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
{
var result = Service.UpdateAll(updatedRequests); return Response.AsJson(result
var result = Service.BatchUpdate(updatedRequests); return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not approve all of the requests. Please try again or check the logs." });
@ -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;
}

@ -45,7 +45,7 @@ namespace PlexRequests.UI.Modules
public class RequestsModule : BaseModule
{
public RequestsModule(IRepository<RequestedModel> service, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<PlexSettings> plex) : base("requests")
public RequestsModule(IRequestService service, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<PlexSettings> plex) : base("requests")
{
Service = service;
PrSettings = prSettings;
@ -64,7 +64,7 @@ namespace PlexRequests.UI.Modules
Post["/addnote"] = _ => AddNote((int)Request.Form.requestId, (string)Request.Form.noteArea);
}
private IRepository<RequestedModel> Service { get; }
private IRequestService Service { get; }
private ISettingsService<PlexRequestSettings> PrSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
@ -114,7 +114,7 @@ namespace PlexRequests.UI.Modules
Status = tv.Status,
ImdbId = tv.ImdbId,
Id = tv.Id,
PosterPath = tv.ProviderId.ToString(),
PosterPath = tv.PosterPath,
ReleaseDate = tv.ReleaseDate.Humanize(),
RequestedDate = tv.RequestedDate.Humanize(),
Approved = tv.Approved,
@ -140,7 +140,7 @@ namespace PlexRequests.UI.Modules
}
var currentEntity = Service.Get(requestid);
Service.Delete(currentEntity);
Service.DeleteRequest(currentEntity);
return Response.AsJson(new JsonResponseModel { Result = true });
}
@ -165,7 +165,7 @@ namespace PlexRequests.UI.Modules
: string.Empty;
var result = Service.Update(originalRequest);
var result = Service.UpdateRequest(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
@ -186,7 +186,7 @@ namespace PlexRequests.UI.Modules
originalRequest.Issues = IssueState.None;
originalRequest.OtherMessage = string.Empty;
var result = Service.Update(originalRequest);
var result = Service.UpdateRequest(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" });
@ -202,7 +202,7 @@ namespace PlexRequests.UI.Modules
originalRequest.Available = available;
var result = Service.Update(originalRequest);
var result = Service.UpdateRequest(originalRequest);
return Response.AsJson(result
? new { Result = true, Available = available, Message = string.Empty }
: new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
@ -218,7 +218,7 @@ namespace PlexRequests.UI.Modules
originalRequest.AdminNote = noteArea;
var result = Service.Update(originalRequest);
var result = Service.UpdateRequest(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not update the notes, please try again or check the logs" });

@ -26,6 +26,9 @@
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Nancy;
using Nancy.Responses.Negotiation;
@ -39,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
@ -48,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;
@ -60,6 +64,8 @@ namespace PlexRequests.UI.Modules
SonarrApi = sonarrApi;
SonarrService = sonarrSettings;
CouchPotatoApi = cpApi;
SickRageService = sickRageService;
SickrageApi = srApi;
Get["/"] = parameters => RequestLoad();
@ -76,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);
@ -104,41 +112,34 @@ namespace PlexRequests.UI.Modules
private Response SearchTvShow(string searchTerm)
{
Log.Trace("Searching for TV Show {0}", searchTerm);
var tvShow = TvApi.SearchTv(searchTerm, AuthToken);
if (tvShow?.data == null)
//var tvShow = TvApi.SearchTv(searchTerm, AuthToken);
var tvShow = new TvMazeApi().Search(searchTerm);
if (!tvShow.Any())
{
Log.Trace("TV Show data is null");
return Response.AsJson("");
}
var model = new List<SearchTvShowViewModel>();
foreach (var t in tvShow.data)
foreach (var t in tvShow)
{
model.Add(new SearchTvShowViewModel
{
Added = t.added,
AirsDayOfWeek = t.airsDayOfWeek,
AirsTime = t.airsTime,
Aliases = t.aliases,
// We are constructing the banner with the id:
// http://thetvdb.com/banners/_cache/posters/ID-1.jpg
Banner = t.id.ToString(),
FirstAired = t.firstAired,
Genre = t.genre,
Id = t.id,
ImdbId = t.imdbId,
LastUpdated = t.lastUpdated,
Network = t.network,
NetworkId = t.networkId,
Overview = t.overview,
Rating = t.rating,
Runtime = t.runtime,
SeriesId = t.id,
SeriesName = t.seriesName,
SiteRating = t.siteRating,
Status = t.status,
Zap2ItId = t.zap2itId
Banner = t.show.image?.medium,
FirstAired = t.show.premiered,
Id = t.show.externals?.thetvdb ?? 0,
ImdbId = t.show.externals?.imdb,
Network = t.show.network?.name,
NetworkId = t.show.network?.id.ToString(),
Overview = t.show.summary.RemoveHtml(),
Rating = t.score.ToString(CultureInfo.CurrentUICulture),
Runtime = t.show.runtime.ToString(),
SeriesId = t.show.id,
SeriesName = t.show.name,
Status = t.show.status,
});
}
@ -190,12 +191,12 @@ namespace PlexRequests.UI.Modules
Log.Trace("Getting movie info from TheMovieDb");
Log.Trace(movieInfo.DumpJson);
#if !DEBUG
if (CheckIfTitleExistsInPlex(movieInfo.Title))
//#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
{
@ -219,13 +220,13 @@ 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)
{
model.Approved = true;
Log.Debug("Adding movie to database requests (No approval required)");
RequestService.AddRequest(movieId, model);
RequestService.AddRequest(model);
return Response.AsJson(new JsonResponseModel { Result = true });
}
@ -235,7 +236,7 @@ namespace PlexRequests.UI.Modules
try
{
Log.Debug("Adding movie to database requests");
var id = RequestService.AddRequest(movieId, model);
var id = RequestService.AddRequest(model);
NotificationService.Publish(model.Title, model.RequestedBy);
@ -262,28 +263,27 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = "TV Show has already been requested!" });
}
var tvApi = new TheTvDbApi();
var token = GetTvDbAuthToken(tvApi);
var tvApi = new TvMazeApi();
var showInfo = tvApi.GetInformation(showId, token).data;
var showInfo = tvApi.ShowLookupByTheTvDbId(showId);
#if !DEBUG
if (CheckIfTitleExistsInPlex(showInfo.seriesName))
//#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.seriesName} is already in Plex!" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.name} is already in Plex!" });
}
#endif
//#endif
DateTime firstAir;
DateTime.TryParse(showInfo.firstAired, out firstAir);
DateTime.TryParse(showInfo.premiered, out firstAir);
var model = new RequestedModel
{
ProviderId = showInfo.id,
ProviderId = showInfo.externals?.thetvdb ?? 0,
Type = RequestType.TvShow,
Overview = showInfo.overview,
PosterPath = "http://image.tmdb.org/t/p/w150/" + showInfo.banner, // This is incorrect
Title = showInfo.seriesName,
Overview = showInfo.summary.RemoveHtml(),
PosterPath = showInfo.image?.medium,
Title = showInfo.name,
ReleaseDate = firstAir,
Status = showInfo.status,
RequestedDate = DateTime.Now,
@ -298,36 +298,70 @@ 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(showId, 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(showId, model);
RequestService.AddRequest(model);
NotificationService.Publish(model.Title, model.RequestedBy);
return Response.AsJson(new { Result = true });
}
private string GetTvDbAuthToken(TheTvDbApi api)
private bool CheckIfTitleExistsInPlex(string title, string year)
{
return Cache.GetOrSet(CacheKeys.TvDbToken, api.Authenticate, 50);
var result = Checker.IsAvailable(title, year);
return result;
}
private bool CheckIfTitleExistsInPlex(string title)
private Response SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model)
{
var result = Checker.IsAvailable(title);
return result;
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." });
}
}
}

@ -24,6 +24,9 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using Nancy;
@ -98,15 +101,28 @@ namespace PlexRequests.UI.Modules
var signedIn = (PlexAuthentication)Api.SignIn(username, password);
if (signedIn.user?.authentication_token != null)
{
Log.Debug("Correct credentials, checking if the user is in the friends list");
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (CheckIfUserIsOwner(settings.PlexAuthToken, signedIn.user?.username))
{
Log.Debug("User is the account owner");
authenticated = true;
}
else
{
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
}
}
}
else if(settings.UserAuthentication) // Check against the users in Plex
{
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!
@ -127,6 +143,8 @@ namespace PlexRequests.UI.Modules
: new JsonResponseModel { Result = false, Message = "Incorrect User or Password"});
}
private Response Logout()
{
Log.Debug("Logging Out");
@ -137,12 +155,23 @@ namespace PlexRequests.UI.Modules
return Context.GetRedirect("~/userlogin");
}
private bool CheckIfUserIsOwner(string authToken, string userName)
{
var userAccount = Api.GetAccount(authToken);
if (userAccount == null)
{
return false;
}
return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase);
}
private bool CheckIfUserIsInPlexFriends(string username, string authToken)
{
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>
@ -320,6 +322,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,11 +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"
};
@ -81,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();
}
@ -101,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.");
}
}
}

@ -37,8 +37,35 @@
<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">
<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>
<button id="testCp" type="submit" class="btn btn-primary-outline">Test Connectivity</button>
@ -62,9 +89,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",
@ -94,9 +170,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) {
@ -112,6 +192,6 @@
}
});
});
});
</script>

@ -0,0 +1,172 @@
@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="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="">
@ -37,7 +50,20 @@
<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">
<div>
<button type="submit" id="getProfiles" class="btn btn-primary-outline">Get Quality Profiles</button>
@ -108,7 +134,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>");
@ -120,11 +146,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")
{

@ -62,7 +62,7 @@
{{/if_eq}}
{{#if_eq type "tv"}}
{{#if posterPath}}
<img class="img-responsive" width="150" src="http://thetvdb.com/banners/_cache/posters/{{posterPath}}-1.jpg" alt="poster">
<img class="img-responsive" width="150" src="{{posterPath}}" alt="poster">
{{/if}}
{{/if_eq}}
</div>
@ -95,14 +95,14 @@
</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}}
<div>Issue: {{issues}}</div>
{{/if}}
</div>
<div id="adminNotesArea">
<div id="adminNotesArea{{requestId}}">
{{#if adminNote}}
<div>Note from Admin: {{adminNote}}</div>
{{/if}}

@ -69,7 +69,7 @@
{{/if_eq}}
{{#if_eq type "tv"}}
{{#if posterPath}}
<img class="img-responsive" width="150" src="http://thetvdb.com/banners/_cache/posters/{{posterPath}}-1.jpg" alt="poster">
<img class="img-responsive" width="150" src="{{posterPath}}" alt="poster">
{{/if}}
{{/if_eq}}

@ -6,6 +6,7 @@
<head>
<title>Plex Requests</title>
<!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="~/Content/custom.min.css" type="text/css"/>
<link rel="stylesheet" href="~/Content/bootstrap.css" type="text/css"/>
<link rel="stylesheet" href="~/Content/font-awesome.css" type="text/css"/>
@ -89,4 +90,4 @@
@RenderBody()
</div>
</body>
</html>
</html>

@ -12,7 +12,8 @@ I wanted to write a similar application in .Net!
#Features
* Integration with [TheMovieDB](https://www.themoviedb.org/) for all Movies and TV shows
* Integration with [TheMovieDB](https://www.themoviedb.org/) for all Movies
* Integration with [TVMaze](www.tvmaze.com) for all TV shows!
* Secure authentication
* [Sonarr](https://sonarr.tv/) integration (SickRage/Sickbeard TBD)
* [CouchPotato](https://couchpota.to/) integration

@ -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.0'
assembly_file_version: '{version}'
assembly_informational_version: '1.3.0'
assembly_informational_version: '1.4.0'
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