diff --git a/PlexRequests.Api.Interfaces/IPlexApi.cs b/PlexRequests.Api.Interfaces/IPlexApi.cs index 2bc43202d..baad57aad 100644 --- a/PlexRequests.Api.Interfaces/IPlexApi.cs +++ b/PlexRequests.Api.Interfaces/IPlexApi.cs @@ -26,7 +26,6 @@ #endregion using System; -using PlexRequests.Api.Models; using PlexRequests.Api.Models.Plex; namespace PlexRequests.Api.Interfaces @@ -43,5 +42,6 @@ namespace PlexRequests.Api.Interfaces PlexMetadata GetMetadata(string authToken, Uri plexFullHost, string itemId); PlexEpisodeMetadata GetEpisodeMetaData(string authToken, Uri host, string ratingKey); PlexSearch GetAllEpisodes(string authToken, Uri host, string section, int startPage, int returnCount); + PlexServer GetServer(string authToken); } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Plex/PlexServer.cs b/PlexRequests.Api.Models/Plex/PlexServer.cs new file mode 100644 index 000000000..818f88556 --- /dev/null +++ b/PlexRequests.Api.Models/Plex/PlexServer.cs @@ -0,0 +1,85 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexServer.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; +using System.Xml.Serialization; + +namespace PlexRequests.Api.Models.Plex +{ + [XmlRoot(ElementName = "Server")] + public class ServerInfo + { + [XmlAttribute(AttributeName = "accessToken")] + public string AccessToken { get; set; } + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + [XmlAttribute(AttributeName = "address")] + public string Address { get; set; } + [XmlAttribute(AttributeName = "port")] + public string Port { get; set; } + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + [XmlAttribute(AttributeName = "scheme")] + public string Scheme { get; set; } + [XmlAttribute(AttributeName = "host")] + public string Host { get; set; } + [XmlAttribute(AttributeName = "localAddresses")] + public string LocalAddresses { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "createdAt")] + public string CreatedAt { get; set; } + [XmlAttribute(AttributeName = "updatedAt")] + public string UpdatedAt { get; set; } + [XmlAttribute(AttributeName = "owned")] + public string Owned { get; set; } + [XmlAttribute(AttributeName = "synced")] + public string Synced { get; set; } + [XmlAttribute(AttributeName = "sourceTitle")] + public string SourceTitle { get; set; } + [XmlAttribute(AttributeName = "ownerId")] + public string OwnerId { get; set; } + [XmlAttribute(AttributeName = "home")] + public string Home { get; set; } + } + + [XmlRoot(ElementName = "MediaContainer")] + public class PlexServer + { + [XmlElement(ElementName = "Server")] + public List Server { get; set; } + [XmlAttribute(AttributeName = "friendlyName")] + public string FriendlyName { get; set; } + [XmlAttribute(AttributeName = "identifier")] + public string Identifier { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "size")] + public string Size { get; set; } + } + + +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj index bb41eb769..20363d996 100644 --- a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj +++ b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj @@ -69,6 +69,7 @@ + diff --git a/PlexRequests.Api/PlexApi.cs b/PlexRequests.Api/PlexApi.cs index 91df2ce11..d4ffa450f 100644 --- a/PlexRequests.Api/PlexApi.cs +++ b/PlexRequests.Api/PlexApi.cs @@ -56,6 +56,7 @@ namespace PlexRequests.Api private const string SignInUri = "https://plex.tv/users/sign_in.json"; private const string FriendsUri = "https://plex.tv/pms/friends/all"; private const string GetAccountUri = "https://plex.tv/users/account"; + private const string ServerUri = "https://plex.tv/pms/servers.xml"; private static Logger Log = LogManager.GetCurrentClassLogger(); private static string Version { get; } @@ -87,18 +88,18 @@ namespace PlexRequests.Api public PlexFriends GetUsers(string authToken) { - var request = new RestRequest - { - Method = Method.GET, - }; + var request = new RestRequest + { + Method = Method.GET, + }; - AddHeaders(ref request, authToken); + AddHeaders(ref request, authToken); - var users = RetryHandler.Execute(() => Api.ExecuteXml (request, new Uri(FriendsUri)), - (exception, timespan) => Log.Error (exception, "Exception when calling GetUsers for Plex, Retrying {0}", timespan), null); + var users = RetryHandler.Execute(() => Api.ExecuteXml (request, new Uri(FriendsUri)), + (exception, timespan) => Log.Error (exception, "Exception when calling GetUsers for Plex, Retrying {0}", timespan), null); - return users; + return users; } /// @@ -300,6 +301,22 @@ namespace PlexRequests.Api } } + public PlexServer GetServer(string authToken) + { + var request = new RestRequest + { + Method = Method.GET, + }; + + AddHeaders(ref request, authToken); + + var servers = RetryHandler.Execute(() => Api.ExecuteXml(request, new Uri(ServerUri)), + (exception, timespan) => Log.Error(exception, "Exception when calling GetServer for Plex, Retrying {0}", timespan)); + + + return servers; + } + private void AddHeaders(ref RestRequest request, string authToken) { request.AddHeader("X-Plex-Token", authToken); diff --git a/PlexRequests.UI/Content/wizard.js b/PlexRequests.UI/Content/wizard.js new file mode 100644 index 000000000..88369b2b3 --- /dev/null +++ b/PlexRequests.UI/Content/wizard.js @@ -0,0 +1,140 @@ +$(function () { + + // Step 1 + $('#firstNext') + .click(function () { + loadArea("plexAuthArea"); + }); + + // Step 2 - Get the auth token + $('#contentBody').on('click', '#requestToken', function (e) { + e.preventDefault(); + + var $form = $("#plexAuthForm"); + $.post($form.prop("action"), $form.serialize(), function (response) { + if (response.result === true) { + loadArea("plexArea"); + + if (response.port) { + $('#portNumber').val(response.port); + } + if (response.ip) { + $('#Ip').val(response.ip); + } + if (response.scheme) { + response.scheme === "http" ? $('#Ssl').prop('checked', false) : $('#Ssl').prop('checked', true); + } + } else { + generateNotify(response.message, "warning"); + } + }); + }); + + // Step 3 - Submit the Plex Details + $('#contentBody').on('click', '#submitPlex', function (e) { + e.preventDefault(); + var $form = $("#plexForm"); + $.ajax({ + type: $form.prop("method"), + url: $form.prop("action"), + data: $form.serialize(), + dataType: "json", + success: function (response) { + if (response.result === true) { + //Next + loadArea("plexRequestArea"); + } else { + generateNotify(response.message, "warning"); + } + }, + error: function (e) { + console.log(e); + } + }); + }); + + // Step 4 - Submit the Plex Request Settings + $('#contentBody').on('click', '#submitPlexRequest', function (e) { + e.preventDefault(); + var $form = $("#plexRequestForm"); + $.ajax({ + type: $form.prop("method"), + url: $form.prop("action"), + data: $form.serialize(), + dataType: "json", + success: function (response) { + if (response.result === true) { + //Next + loadArea("authArea"); + $('.userAuthTooltip').tooltipster({ + theme: 'borderless' + }); + $('.passwordAuthTooltip').tooltipster({ + theme: 'borderless' + }); + } else { + generateNotify(response.message, "warning"); + } + }, + error: function (e) { + console.log(e); + } + }); + }); + + // Step 5 - Plex Requests Authentication Settings + $('#contentBody').on('click', '#submitAuth', function (e) { + e.preventDefault(); + var $form = $("#authForm"); + $.ajax({ + type: $form.prop("method"), + url: $form.prop("action"), + data: $form.serialize(), + dataType: "json", + success: function (response) { + if (response.result === true) { + //Next + loadArea("createAdminArea"); + } else { + generateNotify(response.message, "warning"); + } + }, + error: function (e) { + console.log(e); + } + }); + }); + + $('#contentBody').on('click', '#SearchForMovies', function () { + var checked = this.checked; + changeDisabledStatus($('#RequireMovieApproval'), checked, $('#RequireMovieApprovalLabel')); + }); + + $('#contentBody').on('click', '#SearchForTvShows', function () { + var checked = this.checked; + changeDisabledStatus($('#RequireTvShowApproval'), checked, $('#RequireTvShowApprovalLabel')); + }); + + $('#contentBody').on('click', '#SearchForMusic', function () { + var checked = this.checked; + changeDisabledStatus($('#RequireMusicApproval'), checked, $('#RequireMusicApprovalLabel')); + }); + + function changeDisabledStatus($element, checked, $label) { + if (checked) { + $element.removeAttr("disabled"); + $label.css("color",""); + } else { + $element.attr("disabled","disabled"); + $label.css("color", "grey"); + } + } + + function loadArea(templateId) { + var $body = $('#contentBody'); + + var templateSource = $("#" + templateId).html(); + // Do some sliding? + $body.html(templateSource); + } +}); \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs index 199ba20b9..1203cee63 100644 --- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs +++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs @@ -91,7 +91,7 @@ namespace PlexRequests.UI.Helpers var scriptAssets = new List { $"", - $"", + //$"", $"", $"", $"", @@ -154,6 +154,18 @@ namespace PlexRequests.UI.Helpers return helper.Raw(sb.ToString()); } + public static IHtmlString LoadWizardAssets(this HtmlHelpers helper) + { + var sb = new StringBuilder(); + var assetLocation = GetBaseUrl(); + + var content = GetContentUrl(assetLocation); + + sb.AppendLine($""); + + return helper.Raw(sb.ToString()); + } + public static IHtmlString LoadIssueDetailsAssets(this HtmlHelpers helper) { var assetLocation = GetBaseUrl(); diff --git a/PlexRequests.UI/Models/SessionKeys.cs b/PlexRequests.UI/Models/SessionKeys.cs index 949441650..fefc7d8bd 100644 --- a/PlexRequests.UI/Models/SessionKeys.cs +++ b/PlexRequests.UI/Models/SessionKeys.cs @@ -1,34 +1,35 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SessionKeys.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.UI.Models -{ - public class SessionKeys - { - public const string UsernameKey = "Username"; - public const string ClientDateTimeOffsetKey = "ClientDateTimeOffset"; - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SessionKeys.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.UI.Models +{ + public class SessionKeys + { + public const string UsernameKey = "Username"; + public const string ClientDateTimeOffsetKey = "ClientDateTimeOffset"; + public const string UserWizardPlexAuth = nameof(UserWizardPlexAuth); + } +} diff --git a/PlexRequests.UI/Modules/UserWizardModule.cs b/PlexRequests.UI/Modules/UserWizardModule.cs new file mode 100644 index 000000000..e08f3fee3 --- /dev/null +++ b/PlexRequests.UI/Modules/UserWizardModule.cs @@ -0,0 +1,145 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserWizardModule.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.Linq; +using System.Threading.Tasks; + +using Nancy; +using Nancy.ModelBinding; +using Nancy.Responses.Negotiation; +using Nancy.Validation; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.UI.Helpers; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules +{ + public class UserWizardModule : BaseModule + { + public UserWizardModule(ISettingsService pr, ISettingsService plex, IPlexApi plexApi, + ISettingsService auth) : base("wizard", pr) + { + PlexSettings = plex; + PlexApi = plexApi; + PlexRequestSettings = pr; + Auth = auth; + + Get["/"] = x => Index(); + Post["/plexAuth"] = x => PlexAuth(); + Post["/plex", true] = async (x,ct) => await Plex(); + Post["/plexrequest", true] = async (x,ct) => await PlexRequest(); + Post["/auth", true] = async (x,ct) => await Authentication(); + } + + private ISettingsService PlexSettings { get; } + private IPlexApi PlexApi { get; } + private ISettingsService PlexRequestSettings { get; } + private ISettingsService Auth { get; } + + private Negotiator Index() + { + return View["Index"]; + } + + + + + private Response PlexAuth() + { + var user = this.Bind(); + + if (string.IsNullOrEmpty(user.username) || string.IsNullOrEmpty(user.password)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Please provide a valid username and password" }); + } + + var model = PlexApi.SignIn(user.username, user.password); + + if (model?.user == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect username or password!" }); + } + + // Set the auth token in the session so we can use it in the next form + Session[SessionKeys.UserWizardPlexAuth] = model.user.authentication_token; + + var servers = PlexApi.GetServer(model.user.authentication_token); + var firstServer = servers.Server.FirstOrDefault(); + return Response.AsJson(new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme }); + } + + private async Task Plex() + { + var form = this.Bind(); + var valid = this.Validate(form); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString(); // Set the auth token from the previous form + + var result = await PlexSettings.SaveSettingsAsync(form); + if (result) + { + return Response.AsJson(new JsonResponseModel { Result = true }); + } + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." }); + } + + private async Task PlexRequest() + { + var form = this.Bind(); + var valid = this.Validate(form); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = await PlexRequestSettings.SaveSettingsAsync(form); + if (result) + { + return Response.AsJson(new { Result = true }); + } + + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." }); + } + + private async Task Authentication() + { + var form = this.Bind(); + + var result = await Auth.SaveSettingsAsync(form); + if (result) + { + return Response.AsJson(new JsonResponseModel { Result = true }); + } + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." }); + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index f505fdab5..713c67f57 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -251,6 +251,7 @@ + @@ -327,6 +328,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -690,6 +694,9 @@ Always + + Always + web.config diff --git a/PlexRequests.UI/Views/Admin/Authentication.cshtml b/PlexRequests.UI/Views/Admin/Authentication.cshtml index e66b55172..74c5070f2 100644 --- a/PlexRequests.UI/Views/Admin/Authentication.cshtml +++ b/PlexRequests.UI/Views/Admin/Authentication.cshtml @@ -65,7 +65,7 @@

A comma separated list of users that you do not want to login.

- +
diff --git a/PlexRequests.UI/Views/UserWizard/Index.cshtml b/PlexRequests.UI/Views/UserWizard/Index.cshtml new file mode 100644 index 000000000..2ec4a6610 --- /dev/null +++ b/PlexRequests.UI/Views/UserWizard/Index.cshtml @@ -0,0 +1,152 @@ + +@using PlexRequests.UI.Helpers +@inherits PlexRequests.UI.Helpers.EmptyViewBase +@{ + var baseUrl = Html.GetBaseUrl(); + var formAction = string.Empty; + if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) + { + formAction = "/" + baseUrl.ToHtmlString(); + } +} +@Html.LoadWizardAssets() + + +
+ + +
+
+

Welcome to Plex Requests

+
+ we are just going to run though the initial Plex Requests setup! + +
+ Next +
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file