Merge pull request #1 from tidusjar/dev

Dev
This commit is contained in:
halali 2016-06-28 18:48:17 +02:00 committed by GitHub
commit 18bc5e8b42
97 changed files with 14104 additions and 8429 deletions

71
.gitattributes vendored Normal file
View file

@ -0,0 +1,71 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
PlexRequests.UI/Content/* linguist-vendored
PlexRequests.UI/Content/* linguist-vendored
base.scss linguist-vendored=false
requests-1.7.js linguist-vendored=false
search-1.7.js linguist-vendored=false
site-1.7.js linguist-vendored=false

View file

@ -136,7 +136,11 @@ namespace PlexRequests.Api
{ {
var request = new RestRequest var request = new RestRequest
{ {
Resource = "/api/{apikey}/movie.list?status={status}" Resource = "/api/{apikey}/movie.list?status={status}",
OnBeforeDeserialization = (x =>
{
x.Content = x.Content.Replace("[]", "{}");
})
}; };
request.AddUrlSegment("apikey", apiKey); request.AddUrlSegment("apikey", apiKey);

View file

@ -24,36 +24,68 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
namespace PlexRequests.Core.Tests namespace PlexRequests.Core.Tests
{ {
[TestFixture] [TestFixture]
public class AuthenticationSettingsTests public class NotificationMessageResolverTests
{ {
[Test, TestCaseSource(nameof(UserData))] [TestCaseSource(nameof(MessageResolver))]
public void DeniedUserListTest(string users, string[] expected) public string Resolve(Dictionary<NotificationType, string> message, Dictionary<string,string> param)
{ {
var model = new AuthenticationSettings { DeniedUsers = users }; var n = new NotificationMessageResolver();
var s = new NotificationSettings
var result = model.DeniedUserList;
Assert.That(result.Count, Is.EqualTo(expected.Length));
for (var i = 0; i < expected.Length; i++)
{ {
Assert.That(result[i], Is.EqualTo(expected[i])); Message = message,
CustomParamaters = param
};
return n.ParseMessage(s, NotificationType.NewRequest);
}
private static IEnumerable<TestCaseData> MessageResolver
{
get
{
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "There has been a new request from {Username}, Title: {Title} for {Type}" } },
new Dictionary<string, string>{{"Username", "Jamie" },{"Title", "Finding Dory" },{"Type", "Movie" }})
.Returns("There has been a new request from Jamie, Title: Finding Dory for Movie")
.SetName("FindingDory");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, string.Empty } },
new Dictionary<string, string>())
.Returns(string.Empty)
.SetName("Empty Message");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "{{Wowwzer}} Damn}{{son}}}}" } },
new Dictionary<string, string> { {"son","HEY!"} })
.Returns("{{Wowwzer}} Damn}{HEY!}}}")
.SetName("Multiple Curlys");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "This is a message with no curlys" } },
new Dictionary<string, string> { { "son", "HEY!" } })
.Returns("This is a message with no curlys")
.SetName("No Curlys");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, new string(')', 5000)} },
new Dictionary<string, string> { { "son", "HEY!" } })
.Returns(new string(')', 5000))
.SetName("Long String");
} }
} }
static readonly object[] UserData =
{
new object[] { "john", new [] {"john"} },
new object[] { "john , abc ,", new [] {"john", "abc"} },
new object[] { "john,, cde", new [] {"john", "cde"} },
new object[] { "john,,, aaa , baaa , ", new [] {"john","aaa","baaa"} },
new object[] { "john, aaa , baaa , maaa, caaa", new [] {"john","aaa","baaa", "maaa", "caaa"} },
};
} }
} }

View file

@ -0,0 +1,59 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AuthenticationSettingsTests.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;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Core.Tests
{
[TestFixture]
public class AuthenticationSettingsTests
{
[Test, TestCaseSource(nameof(UserData))]
public void DeniedUserListTest(string users, string[] expected)
{
var model = new AuthenticationSettings { DeniedUsers = users };
var result = model.DeniedUserList;
Assert.That(result.Count, Is.EqualTo(expected.Length));
for (var i = 0; i < expected.Length; i++)
{
Assert.That(result[i], Is.EqualTo(expected[i]));
}
}
static readonly object[] UserData =
{
new object[] { "john", new [] {"john"} },
new object[] { "john , abc ,", new [] {"john", "abc"} },
new object[] { "john,, cde", new [] {"john", "cde"} },
new object[] { "john,,, aaa , baaa , ", new [] {"john","aaa","baaa"} },
new object[] { "john, aaa , baaa , maaa, caaa", new [] {"john","aaa","baaa", "maaa", "caaa"} },
};
}
}

View file

@ -59,6 +59,7 @@
<Otherwise /> <Otherwise />
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="NotificationMessageResolverTests.cs" />
<Compile Include="StatusCheckerTests.cs" /> <Compile Include="StatusCheckerTests.cs" />
<Compile Include="AuthenticationSettingsTests.cs" /> <Compile Include="AuthenticationSettingsTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -24,7 +24,7 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
namespace PlexRequests.Services.Notification namespace PlexRequests.Core.Models
{ {
public enum NotificationType public enum NotificationType
{ {

View file

@ -0,0 +1,102 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CustomNotificationService.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.Linq;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Core
{
public class NotificationMessageResolver
{
public string ParseMessage<T>(T notification, NotificationType type) where T : NotificationSettings
{
var notificationToParse = notification.Message.FirstOrDefault(x => x.Key == type).Value;
if (string.IsNullOrEmpty(notificationToParse))
return string.Empty;
return Resolve(notificationToParse, notification.CustomParamaters);
}
private string Resolve(string message, Dictionary<string,string> paramaters)
{
var fields = FindCurlyFields(message);
foreach (var f in fields)
{
string outString;
if (paramaters.TryGetValue(f, out outString))
{
message = message.Replace($"{{{f}}}", outString);
}
}
return message;
}
private IEnumerable<string> FindCurlyFields(string message)
{
var insideCurly = false;
var fields = new List<string>();
var currentWord = string.Empty;
var chars = message.ToCharArray();
foreach (var c in chars)
{
if (char.IsWhiteSpace(c))
{
currentWord = string.Empty;
continue;
}
if (c == 123) // Start of curly '{'
{
insideCurly = true;
continue;
}
if (c == 125) // End of curly '}'
{
fields.Add(currentWord); // We have finished the curly, add the word into the list
currentWord = string.Empty;
insideCurly = false;
continue;
}
if (insideCurly)
{
currentWord += c.ToString(); // Add the character onto the word.
}
}
return fields;
}
}
}

View file

@ -67,17 +67,20 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CacheKeys.cs" /> <Compile Include="CacheKeys.cs" />
<Compile Include="NotificationMessageResolver.cs" />
<Compile Include="IIssueService.cs" /> <Compile Include="IIssueService.cs" />
<Compile Include="IRequestService.cs" /> <Compile Include="IRequestService.cs" />
<Compile Include="ISettingsService.cs" /> <Compile Include="ISettingsService.cs" />
<Compile Include="JsonIssuesModelRequestService.cs" /> <Compile Include="JsonIssuesModelRequestService.cs" />
<Compile Include="JsonRequestModelRequestService.cs" /> <Compile Include="JsonRequestModelRequestService.cs" />
<Compile Include="Models\IssuesModel.cs" /> <Compile Include="Models\IssuesModel.cs" />
<Compile Include="Models\NotificationType.cs" />
<Compile Include="Models\StatusModel.cs" /> <Compile Include="Models\StatusModel.cs" />
<Compile Include="Models\UserProperties.cs" /> <Compile Include="Models\UserProperties.cs" />
<Compile Include="SettingModels\AuthenticationSettings.cs" /> <Compile Include="SettingModels\AuthenticationSettings.cs" />
<Compile Include="SettingModels\HeadphonesSettings.cs" /> <Compile Include="SettingModels\HeadphonesSettings.cs" />
<Compile Include="SettingModels\LandingPageSettings.cs" /> <Compile Include="SettingModels\LandingPageSettings.cs" />
<Compile Include="SettingModels\NotificationSettings.cs" />
<Compile Include="SettingModels\ScheduledJobsSettings.cs" /> <Compile Include="SettingModels\ScheduledJobsSettings.cs" />
<Compile Include="SettingModels\SlackNotificationSettings.cs" /> <Compile Include="SettingModels\SlackNotificationSettings.cs" />
<Compile Include="SettingModels\PushoverNotificationSettings.cs" /> <Compile Include="SettingModels\PushoverNotificationSettings.cs" />

View file

@ -26,7 +26,7 @@
#endregion #endregion
namespace PlexRequests.Core.SettingModels namespace PlexRequests.Core.SettingModels
{ {
public class EmailNotificationSettings : Settings public class EmailNotificationSettings : NotificationSettings
{ {
public string EmailHost { get; set; } public string EmailHost { get; set; }
public string EmailPassword { get; set; } public string EmailPassword { get; set; }

View file

@ -1,7 +1,7 @@
#region Copyright #region Copyright
// /************************************************************************ // /************************************************************************
// Copyright (c) 2016 Jamie Rees // Copyright (c) 2016 Jamie Rees
// File: ContainerHelper.cs // File: NotificationSettings.cs
// Created By: Jamie Rees // Created By: Jamie Rees
// //
// Permission is hereby granted, free of charge, to any person obtaining // Permission is hereby granted, free of charge, to any person obtaining
@ -24,25 +24,15 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using Nancy.TinyIoc; using System.Collections.Generic;
using PlexRequests.Core; using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Store;
using PlexRequests.Store.Repository;
namespace PlexRequests.UI.Helpers namespace PlexRequests.Core.SettingModels
{ {
public static class ContainerHelper public class NotificationSettings : Settings
{ {
public static void RegisterSetting<T>(this TinyIoCContainer container) where T : Settings, new() public Dictionary<NotificationType, string> Message { get; set; }
{ public Dictionary<string,string> CustomParamaters { get; set; }
container.Register<ISettingsService<T>, SettingsServiceV2<T>>();
}
public static void RegisterRepo<T>(this TinyIoCContainer container) where T : Entity, new()
{
container.Register<IRepository<T>, GenericRepository<T>>();
}
} }
} }

View file

@ -32,6 +32,13 @@ namespace PlexRequests.Core.SettingModels
{ {
public class PlexRequestSettings : Settings public class PlexRequestSettings : Settings
{ {
public PlexRequestSettings()
{
TvWeeklyRequestLimit = 0;
MovieWeeklyRequestLimit = 0;
AlbumWeeklyRequestLimit = 0;
}
public int Port { get; set; } public int Port { get; set; }
public string BaseUrl { get; set; } public string BaseUrl { get; set; }
public bool SearchForMovies { get; set; } public bool SearchForMovies { get; set; }
@ -42,9 +49,12 @@ namespace PlexRequests.Core.SettingModels
public bool RequireMusicApproval { get; set; } public bool RequireMusicApproval { get; set; }
public bool UsersCanViewOnlyOwnRequests { get; set; } public bool UsersCanViewOnlyOwnRequests { get; set; }
public bool UsersCanViewOnlyOwnIssues { get; set; } public bool UsersCanViewOnlyOwnIssues { get; set; }
public int WeeklyRequestLimit { get; set; } public int MovieWeeklyRequestLimit { get; set; }
public int TvWeeklyRequestLimit { get; set; }
public int AlbumWeeklyRequestLimit { get; set; }
public string NoApprovalUsers { get; set; } public string NoApprovalUsers { get; set; }
public bool CollectAnalyticData { get; set; } public bool CollectAnalyticData { get; set; }
public bool IgnoreNotifyForAutoApprovedRequests { get; set; }
/// <summary> /// <summary>
/// The CSS name of the theme we want /// The CSS name of the theme we want

View file

@ -1,6 +1,6 @@
namespace PlexRequests.Core.SettingModels namespace PlexRequests.Core.SettingModels
{ {
public class PushbulletNotificationSettings : Settings public class PushbulletNotificationSettings : NotificationSettings
{ {
public bool Enabled { get; set; } public bool Enabled { get; set; }
public string AccessToken { get; set; } public string AccessToken { get; set; }

View file

@ -1,6 +1,6 @@
namespace PlexRequests.Core.SettingModels namespace PlexRequests.Core.SettingModels
{ {
public class PushoverNotificationSettings : Settings public class PushoverNotificationSettings : NotificationSettings
{ {
public bool Enabled { get; set; } public bool Enabled { get; set; }
public string AccessToken { get; set; } public string AccessToken { get; set; }

View file

@ -36,6 +36,7 @@ namespace PlexRequests.Core.SettingModels
CouchPotatoCacher = 10; CouchPotatoCacher = 10;
StoreBackup = 24; StoreBackup = 24;
StoreCleanup = 24; StoreCleanup = 24;
UserRequestLimitResetter = 12;
} }
public int PlexAvailabilityChecker { get; set; } public int PlexAvailabilityChecker { get; set; }
public int SickRageCacher { get; set; } public int SickRageCacher { get; set; }
@ -43,5 +44,6 @@ namespace PlexRequests.Core.SettingModels
public int CouchPotatoCacher { get; set; } public int CouchPotatoCacher { get; set; }
public int StoreBackup { get; set; } public int StoreBackup { get; set; }
public int StoreCleanup { get; set; } public int StoreCleanup { get; set; }
public int UserRequestLimitResetter { get; set; }
} }
} }

View file

@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace PlexRequests.Core.SettingModels namespace PlexRequests.Core.SettingModels
{ {
public class SlackNotificationSettings : Settings public class SlackNotificationSettings : NotificationSettings
{ {
public bool Enabled { get; set; } public bool Enabled { get; set; }
public string WebhookUrl { get; set; } public string WebhookUrl { get; set; }

View file

@ -57,10 +57,6 @@ namespace PlexRequests.Core
var version = CheckSchema(); var version = CheckSchema();
if (version > 0) if (version > 0)
{ {
if (version > 1700 && version <= 1799)
{
MigrateToVersion1700();
}
if (version > 1799 && version <= 1800) if (version > 1799 && version <= 1800)
{ {
MigrateToVersion1800(); MigrateToVersion1800();
@ -104,8 +100,8 @@ namespace PlexRequests.Core
RequireMovieApproval = true, RequireMovieApproval = true,
SearchForMovies = true, SearchForMovies = true,
SearchForTvShows = true, SearchForTvShows = true,
WeeklyRequestLimit = 0, BaseUrl = baseUrl ?? string.Empty,
BaseUrl = baseUrl ?? string.Empty CollectAnalyticData = true,
}; };
var s = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); var s = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
s.SaveSettings(defaultSettings); s.SaveSettings(defaultSettings);

View file

@ -0,0 +1,52 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: DateTimeHelperTests.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 NUnit.Framework;
namespace PlexRequests.Helpers.Tests
{
[TestFixture]
public class CookieHelperTests
{
[TestCaseSource(nameof(GetAnalyticsClientId))]
public string TestGetAnalyticsClientId(Dictionary<string,string> cookies)
{
return CookieHelper.GetAnalyticClientId(cookies);
}
private static IEnumerable<TestCaseData> GetAnalyticsClientId
{
get
{
yield return new TestCaseData(new Dictionary<string, string>()).Returns(string.Empty);
yield return new TestCaseData(new Dictionary<string, string> { { "_ga", "GA1.1.306549087.1464005217" } }).Returns("306549087.1464005217");
yield return new TestCaseData(new Dictionary<string,string> { {"_ga", "GA1.1.306549087" } }).Returns(string.Empty);
}
}
}
}

View file

@ -71,6 +71,7 @@
</Otherwise> </Otherwise>
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="CookieHelperTests.cs" />
<Compile Include="DateTimeHelperTests.cs" /> <Compile Include="DateTimeHelperTests.cs" />
<Compile Include="PasswordHasherTests.cs" /> <Compile Include="PasswordHasherTests.cs" />
<Compile Include="HtmlRemoverTests.cs" /> <Compile Include="HtmlRemoverTests.cs" />

View file

@ -34,6 +34,12 @@ namespace PlexRequests.Helpers.Analytics
Create, Create,
Save, Save,
Update, Update,
Start Start,
View,
Movie,
TvShow,
Album,
Request,
Language
} }
} }

View file

@ -47,52 +47,51 @@ namespace PlexRequests.Helpers.Analytics
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
public void TrackEvent(Category category, Action action, string label, string username, int? value = null) public void TrackEvent(Category category, Action action, string label, string username, string clientId, int? value = null)
{ {
var cat = category.ToString(); var cat = category.ToString();
var act = action.ToString(); var act = action.ToString();
Track(HitType.@event, username, cat, act, label, value); Track(HitType.@event, username, cat, act, label, clientId, value);
} }
public async Task TrackEventAsync(Category category, Action action, string label, string username, int? value = null) public async Task TrackEventAsync(Category category, Action action, string label, string username, string clientId, int? value = null)
{ {
var cat = category.ToString(); var cat = category.ToString();
var act = action.ToString(); var act = action.ToString();
await TrackAsync(HitType.@event, username, cat, act, label, value); await TrackAsync(HitType.@event, username, cat, act, clientId, label, value);
} }
public void TrackPageview(Category category, Action action, string label, string username, int? value = null) public void TrackPageview(Category category, Action action, string label, string username, string clientId, int? value = null)
{ {
var cat = category.ToString(); var cat = category.ToString();
var act = action.ToString(); var act = action.ToString();
Track(HitType.@pageview, username, cat, act, label, value); Track(HitType.@pageview, username, cat, act, clientId, label, value);
} }
public async Task TrackPageviewAsync(Category category, Action action, string label, string username, int? value = null) public async Task TrackPageviewAsync(Category category, Action action, string label, string username, string clientId, int? value = null)
{ {
var cat = category.ToString(); var cat = category.ToString();
var act = action.ToString(); var act = action.ToString();
await TrackAsync(HitType.@pageview, username, cat, act, label, value); await TrackAsync(HitType.@pageview, username, cat, act, clientId, label, value);
} }
public void TrackException(string message, string username, bool fatal) public void TrackException(string message, string username, string clientId, bool fatal)
{ {
var fatalInt = fatal ? 1 : 0; var fatalInt = fatal ? 1 : 0;
Track(HitType.exception, message, fatalInt, username); Track(HitType.exception, message, fatalInt, username, clientId);
} }
public async Task TrackExceptionAsync(string message, string username, bool fatal) public async Task TrackExceptionAsync(string message, string username, string clientId, bool fatal)
{ {
var fatalInt = fatal ? 1 : 0; var fatalInt = fatal ? 1 : 0;
await TrackAsync(HitType.exception, message, fatalInt, username); await TrackAsync(HitType.exception, message, fatalInt, username, clientId);
} }
private void Track(HitType type, string username, string category, string action, string label, int? value = null) private void Track(HitType type, string username, string category, string action, string clientId, string label, int? value = null)
{ {
if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category)); if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category));
if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action)); if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
var postData = BuildRequestData(type, username, category, action, label, value, null, null); var postData = BuildRequestData(type, username, category, action, clientId, label, value, null, null);
var postDataString = postData var postDataString = postData
.Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}"))
@ -101,13 +100,12 @@ namespace PlexRequests.Helpers.Analytics
SendRequest(postDataString); SendRequest(postDataString);
} }
private async Task TrackAsync(HitType type, string username, string category, string action, string label, int? value = null) private async Task TrackAsync(HitType type, string username, string category, string action, string clientId, string label, int? value = null)
{ {
if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category)); if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category));
if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action)); if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
var postData = BuildRequestData(type, username, category, action, label, value, null, null); var postData = BuildRequestData(type, username, category, action, clientId, label, value, null, null);
var postDataString = postData var postDataString = postData
.Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}"))
@ -115,12 +113,11 @@ namespace PlexRequests.Helpers.Analytics
await SendRequestAsync(postDataString); await SendRequestAsync(postDataString);
} }
private async Task TrackAsync(HitType type, string message, int fatal, string username) private async Task TrackAsync(HitType type, string message, int fatal, string username, string clientId)
{ {
if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message));
if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
var postData = BuildRequestData(type, username, null, null, null, null, message, fatal); var postData = BuildRequestData(type, username, null, null, null, clientId, null, message, fatal);
var postDataString = postData var postDataString = postData
.Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}"))
@ -129,12 +126,12 @@ namespace PlexRequests.Helpers.Analytics
await SendRequestAsync(postDataString); await SendRequestAsync(postDataString);
} }
private void Track(HitType type, string message, int fatal, string username) private void Track(HitType type, string message, int fatal, string username, string clientId)
{ {
if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message));
if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
var postData = BuildRequestData(type, username, null, null, null, null, message, fatal); var postData = BuildRequestData(type, username, null, null, null, clientId, null, message, fatal);
var postDataString = postData var postDataString = postData
.Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}"))
@ -196,20 +193,24 @@ namespace PlexRequests.Helpers.Analytics
} }
} }
private Dictionary<string, string> BuildRequestData(HitType type, string username, string category, string action, string label, int? value, string exceptionDescription, int? fatal) private Dictionary<string, string> BuildRequestData(HitType type, string username, string category, string action, string clientId, string label, int? value, string exceptionDescription, int? fatal)
{ {
var postData = new Dictionary<string, string> var postData = new Dictionary<string, string>
{ {
{ "v", "1" }, { "v", "1" },
{ "tid", TrackingId }, { "tid", TrackingId },
{ "t", type.ToString() }, { "t", type.ToString() }
{"cid", Guid.NewGuid().ToString() }
}; };
if (!string.IsNullOrEmpty(username)) if (!string.IsNullOrEmpty(username))
{ {
postData.Add("uid", username); postData.Add("uid", username);
} }
postData.Add("cid", !string.IsNullOrEmpty(clientId)
? clientId
: Guid.NewGuid().ToString());
if (!string.IsNullOrEmpty(label)) if (!string.IsNullOrEmpty(label))
{ {
postData.Add("el", label); postData.Add("el", label);

View file

@ -37,5 +37,6 @@ namespace PlexRequests.Helpers.Analytics
Issues, Issues,
UserLogin, UserLogin,
Services, Services,
Navbar
} }
} }

View file

@ -37,8 +37,9 @@ namespace PlexRequests.Helpers.Analytics
/// <param name="action">The action.</param> /// <param name="action">The action.</param>
/// <param name="label">The label.</param> /// <param name="label">The label.</param>
/// <param name="username">The username.</param> /// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
void TrackEvent(Category category, Action action, string label, string username, int? value = null); void TrackEvent(Category category, Action action, string label, string username, string clientId, int? value = null);
/// <summary> /// <summary>
/// Tracks the event asynchronous. /// Tracks the event asynchronous.
@ -47,9 +48,10 @@ namespace PlexRequests.Helpers.Analytics
/// <param name="action">The action.</param> /// <param name="action">The action.</param>
/// <param name="label">The label.</param> /// <param name="label">The label.</param>
/// <param name="username">The username.</param> /// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <returns></returns> /// <returns></returns>
Task TrackEventAsync(Category category, Action action, string label, string username, int? value = null); Task TrackEventAsync(Category category, Action action, string label, string username, string clientId, int? value = null);
/// <summary> /// <summary>
/// Tracks the page view. /// Tracks the page view.
@ -58,8 +60,9 @@ namespace PlexRequests.Helpers.Analytics
/// <param name="action">The action.</param> /// <param name="action">The action.</param>
/// <param name="label">The label.</param> /// <param name="label">The label.</param>
/// <param name="username">The username.</param> /// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
void TrackPageview(Category category, Action action, string label, string username, int? value = null); void TrackPageview(Category category, Action action, string label, string username, string clientId, int? value = null);
/// <summary> /// <summary>
/// Tracks the page view asynchronous. /// Tracks the page view asynchronous.
@ -68,25 +71,28 @@ namespace PlexRequests.Helpers.Analytics
/// <param name="action">The action.</param> /// <param name="action">The action.</param>
/// <param name="label">The label.</param> /// <param name="label">The label.</param>
/// <param name="username">The username.</param> /// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <returns></returns> /// <returns></returns>
Task TrackPageviewAsync(Category category, Action action, string label, string username, int? value = null); Task TrackPageviewAsync(Category category, Action action, string label, string username, string clientId, int? value = null);
/// <summary> /// <summary>
/// Tracks the exception. /// Tracks the exception.
/// </summary> /// </summary>
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
/// <param name="username">The username.</param> /// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="fatal">if set to <c>true</c> [fatal].</param> /// <param name="fatal">if set to <c>true</c> [fatal].</param>
void TrackException(string message, string username, bool fatal); void TrackException(string message, string username, string clientId, bool fatal);
/// <summary> /// <summary>
/// Tracks the exception asynchronous. /// Tracks the exception asynchronous.
/// </summary> /// </summary>
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
/// <param name="username">The username.</param> /// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="fatal">if set to <c>true</c> [fatal].</param> /// <param name="fatal">if set to <c>true</c> [fatal].</param>
/// <returns></returns> /// <returns></returns>
Task TrackExceptionAsync(string message, string username, bool fatal); Task TrackExceptionAsync(string message, string username, string clientId, bool fatal);
} }
} }

View file

@ -0,0 +1,57 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CookieHelper.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;
namespace PlexRequests.Helpers
{
public static class CookieHelper
{
private const string GaCookie = "_ga";
/// <summary>
/// Gets the analytic client identifier.
/// <para>Example: Value = "GA1.1.306549087.1464005217"</para>
/// </summary>
/// <param name="cookies">The cookies.</param>
/// <returns></returns>
public static string GetAnalyticClientId(IDictionary<string, string> cookies)
{
var outString = string.Empty;
if (cookies.TryGetValue(GaCookie, out outString))
{
var split = outString.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
return split.Length < 4
? string.Empty
: $"{split[2]}.{split[3]}";
}
return string.Empty;
}
}
}

View file

@ -69,6 +69,7 @@
<Compile Include="Analytics\IAnalytics.cs" /> <Compile Include="Analytics\IAnalytics.cs" />
<Compile Include="AssemblyHelper.cs" /> <Compile Include="AssemblyHelper.cs" />
<Compile Include="ByteConverterHelper.cs" /> <Compile Include="ByteConverterHelper.cs" />
<Compile Include="CookieHelper.cs" />
<Compile Include="DateTimeHelper.cs" /> <Compile Include="DateTimeHelper.cs" />
<Compile Include="Exceptions\ApiRequestException.cs" /> <Compile Include="Exceptions\ApiRequestException.cs" />
<Compile Include="Exceptions\ApplicationSettingsException.cs" /> <Compile Include="Exceptions\ApplicationSettingsException.cs" />

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9C266462-BE82-461A-87A2-9EDCFB95D732}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Resources</RootNamespace>
<AssemblyName>PlexRequests.Resources</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("PlexRequests.Resources")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PlexRequests.Resources")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("9c266462-be82-461a-87a2-9edcfb95d732")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -24,6 +24,8 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System;
using System.Linq; using System.Linq;
using NLog; using NLog;
@ -86,8 +88,18 @@ namespace PlexRequests.Services.Jobs
// we do not want to set here... // we do not want to set here...
public int[] QueuedIds() public int[] QueuedIds()
{ {
var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued); try
return movies?.movies?.Select(x => x.info.tmdb_id).ToArray() ?? new int[] { }; {
var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued);
var items = movies?.movies?.Select(x => x.info?.tmdb_id).Cast<int>().ToArray();
return items ?? new int[] { };
}
catch (Exception e)
{
Log.Error(e);
return new int[] {};
}
} }
public void Execute(IJobExecutionContext context) public void Execute(IJobExecutionContext context)

View file

@ -34,5 +34,6 @@ namespace PlexRequests.Services.Jobs
public const string SrCacher = "SickRage Cacher"; public const string SrCacher = "SickRage Cacher";
public const string PlexChecker = "Plex Availability Cacher"; public const string PlexChecker = "Plex Availability Cacher";
public const string StoreCleanup = "Database Cleanup"; public const string StoreCleanup = "Database Cleanup";
public const string RequestLimitReset = "Request Limit Reset";
} }
} }

View file

@ -33,6 +33,7 @@ using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex; using PlexRequests.Api.Models.Plex;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics; using PlexRequests.Helpers.Analytics;
@ -179,7 +180,8 @@ namespace PlexRequests.Services.Jobs
{ {
if (advanced) if (advanced)
{ {
if (movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) if (!string.IsNullOrEmpty(movie.ProviderId) &&
movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{ {
return true; return true;
} }
@ -225,7 +227,8 @@ namespace PlexRequests.Services.Jobs
{ {
if (advanced) if (advanced)
{ {
if (show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) if (!string.IsNullOrEmpty(show.ProviderId) &&
show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{ {
return true; return true;
} }

View file

@ -0,0 +1,119 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserRequestLimitResetter.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 System.Linq;
using NLog;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using Quartz;
namespace PlexRequests.Services.Jobs
{
public class UserRequestLimitResetter : IJob
{
public UserRequestLimitResetter(IJobRecord record, IRepository<RequestLimit> repo, ISettingsService<PlexRequestSettings> settings)
{
Record = record;
Repo = repo;
Settings = settings;
}
private IJobRecord Record { get; }
private IRepository<RequestLimit> Repo { get; }
private ISettingsService<PlexRequestSettings> Settings { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public void Execute(IJobExecutionContext context)
{
try
{
var settings = Settings.GetSettings();
var users = Repo.GetAll();
var requestLimits = users as RequestLimit[] ?? users.ToArray();
MovieLimit(settings, requestLimits);
TvLimit(settings, requestLimits);
AlbumLimit(settings, requestLimits);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Record.Record(JobNames.RequestLimitReset);
}
}
public void MovieLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.MovieWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.Movie);
}
public void TvLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.TvWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.TvShow);
}
public void AlbumLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.AlbumWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.Album);
}
private void CheckAndDelete(IEnumerable<RequestLimit> allUsers, RequestType type)
{
var users = allUsers.Where(x => x.RequestType == type);
foreach (var u in users)
{
if (u.FirstRequestDate > DateTime.UtcNow.AddDays(-7))
{
Repo.Delete(u);
}
}
}
}
}

View file

@ -33,9 +33,11 @@ using MimeKit;
using NLog; using NLog;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using SmtpClient = MailKit.Net.Smtp.SmtpClient; using SmtpClient = MailKit.Net.Smtp.SmtpClient;
using PlexRequests.Store;
namespace PlexRequests.Services.Notification namespace PlexRequests.Services.Notification
{ {
@ -119,8 +121,8 @@ namespace PlexRequests.Services.Notification
{ {
var message = new MimeMessage var message = new MimeMessage
{ {
Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" },
Subject = $"Plex Requests: New request for {model.Title}!" Subject = $"Plex Requests: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!"
}; };
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));

View file

@ -24,8 +24,11 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using PlexRequests.Store;
using System; using System;
using PlexRequests.Core.Models;
namespace PlexRequests.Services.Notification namespace PlexRequests.Services.Notification
{ {
public class NotificationModel public class NotificationModel
@ -36,5 +39,6 @@ namespace PlexRequests.Services.Notification
public NotificationType NotificationType { get; set; } public NotificationType NotificationType { get; set; }
public string User { get; set; } public string User { get; set; }
public string UserEmail { get; set; } public string UserEmail { get; set; }
public RequestType RequestType { get; set; }
} }
} }

View file

@ -31,8 +31,10 @@ using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
namespace PlexRequests.Services.Notification namespace PlexRequests.Services.Notification
{ {
@ -104,8 +106,8 @@ namespace PlexRequests.Services.Notification
private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings) private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings)
{ {
var message = $"{model.Title} has been requested by user: {model.User}"; var message = $"The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
var pushTitle = $"Plex Requests: {model.Title} has been requested!"; var pushTitle = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} {model.Title} has been requested!";
await Push(settings, message, pushTitle); await Push(settings, message, pushTitle);
} }

View file

@ -31,8 +31,10 @@ using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
namespace PlexRequests.Services.Notification namespace PlexRequests.Services.Notification
{ {
@ -104,7 +106,7 @@ namespace PlexRequests.Services.Notification
private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings) private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings)
{ {
var message = $"Plex Requests: {model.Title} has been requested by user: {model.User}"; var message = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
await Push(settings, message); await Push(settings, message);
} }

View file

@ -32,6 +32,7 @@ using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Notifications; using PlexRequests.Api.Models.Notifications;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;

View file

@ -79,6 +79,7 @@
<Compile Include="Jobs\PlexAvailabilityChecker.cs" /> <Compile Include="Jobs\PlexAvailabilityChecker.cs" />
<Compile Include="Jobs\SickRageCacher.cs" /> <Compile Include="Jobs\SickRageCacher.cs" />
<Compile Include="Jobs\SonarrCacher.cs" /> <Compile Include="Jobs\SonarrCacher.cs" />
<Compile Include="Jobs\UserRequestLimitResetter.cs" />
<Compile Include="Models\PlexAlbum.cs" /> <Compile Include="Models\PlexAlbum.cs" />
<Compile Include="Models\PlexMovie.cs" /> <Compile Include="Models\PlexMovie.cs" />
<Compile Include="Models\PlexTvShow.cs" /> <Compile Include="Models\PlexTvShow.cs" />
@ -92,7 +93,6 @@
<Compile Include="Notification\EmailMessageNotification.cs" /> <Compile Include="Notification\EmailMessageNotification.cs" />
<Compile Include="Notification\NotificationModel.cs" /> <Compile Include="Notification\NotificationModel.cs" />
<Compile Include="Notification\NotificationService.cs" /> <Compile Include="Notification\NotificationService.cs" />
<Compile Include="Notification\NotificationType.cs" />
<Compile Include="Notification\PushoverNotification.cs" /> <Compile Include="Notification\PushoverNotification.cs" />
<Compile Include="Notification\PushbulletNotification.cs" /> <Compile Include="Notification\PushbulletNotification.cs" />
<Compile Include="Notification\SlackNotification.cs" /> <Compile Include="Notification\SlackNotification.cs" />

View file

@ -0,0 +1,34 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexUsers.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.Store.Models
{
public class PlexUsers : Entity
{
public int PlexUserId { get; set; }
public string UserAlias { get; set; }
}
}

View file

@ -0,0 +1,41 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UsersToNotify.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 Dapper.Contrib.Extensions;
namespace PlexRequests.Store.Models
{
[Table("RequestLimit")]
public class RequestLimit : Entity
{
public string Username { get; set; }
public DateTime FirstRequestDate { get; set; }
public int RequestCount { get; set; }
public RequestType RequestType { get; set; }
}
}

View file

@ -64,7 +64,9 @@
<Compile Include="DbConfiguration.cs" /> <Compile Include="DbConfiguration.cs" />
<Compile Include="Entity.cs" /> <Compile Include="Entity.cs" />
<Compile Include="Models\IssueBlobs.cs" /> <Compile Include="Models\IssueBlobs.cs" />
<Compile Include="Models\PlexUsers.cs" />
<Compile Include="Models\ScheduledJobs.cs" /> <Compile Include="Models\ScheduledJobs.cs" />
<Compile Include="Models\RequestLimit.cs" />
<Compile Include="Models\UsersToNotify.cs" /> <Compile Include="Models\UsersToNotify.cs" />
<Compile Include="Repository\BaseGenericRepository.cs" /> <Compile Include="Repository\BaseGenericRepository.cs" />
<Compile Include="Repository\IRequestRepository.cs" /> <Compile Include="Repository\IRequestRepository.cs" />

View file

@ -78,6 +78,24 @@ namespace PlexRequests.Store
Album Album
} }
public static class RequestTypeDisplay
{
public static string GetString(this RequestType type)
{
switch (type)
{
case RequestType.Movie:
return "Movie";
case RequestType.TvShow:
return "TV Show";
case RequestType.Album:
return "Album";
default:
return string.Empty;
}
}
}
public enum IssueState public enum IssueState
{ {
None = 99, None = 99,

View file

@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS Audit
Username varchar(100) NOT NULL, Username varchar(100) NOT NULL,
ChangeType varchar(100) NOT NULL, ChangeType varchar(100) NOT NULL,
OldValue varchar(100), OldValue varchar(100),
NewValue varchar(100) NewValue varchar(100)
); );
CREATE UNIQUE INDEX IF NOT EXISTS Audit_Id ON Audit (Id); CREATE UNIQUE INDEX IF NOT EXISTS Audit_Id ON Audit (Id);
@ -82,3 +82,21 @@ CREATE TABLE IF NOT EXISTS IssueBlobs
Content BLOB NOT NULL Content BLOB NOT NULL
); );
CREATE UNIQUE INDEX IF NOT EXISTS IssueBlobs_Id ON IssueBlobs (Id); CREATE UNIQUE INDEX IF NOT EXISTS IssueBlobs_Id ON IssueBlobs (Id);
CREATE TABLE IF NOT EXISTS RequestLimit
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Username varchar(100) NOT NULL,
FirstRequestDate varchar(100) NOT NULL,
RequestCount INTEGER NOT NULL,
RequestType INTEGER NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS RequestLimit_Id ON RequestLimit (Id);
CREATE TABLE IF NOT EXISTS PlexUsers
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
PlexUserId INTEGER NOT NULL,
UserAlias varchar(100) NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON RequestLimit (Id);

View file

@ -47,6 +47,7 @@ using PlexRequests.Store.Repository;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
using PlexRequests.UI.Modules; using PlexRequests.UI.Modules;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Helpers; using PlexRequests.UI.Helpers;
namespace PlexRequests.UI.Tests namespace PlexRequests.UI.Tests
@ -78,6 +79,7 @@ namespace PlexRequests.UI.Tests
private Mock<ISettingsService<SlackNotificationSettings>> SlackSettings { get; set; } private Mock<ISettingsService<SlackNotificationSettings>> SlackSettings { get; set; }
private Mock<ISettingsService<LandingPageSettings>> LandingPageSettings { get; set; } private Mock<ISettingsService<LandingPageSettings>> LandingPageSettings { get; set; }
private Mock<ISlackApi> SlackApi { get; set; } private Mock<ISlackApi> SlackApi { get; set; }
private Mock<IAnalytics> IAnalytics { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; } private ConfigurableBootstrapper Bootstrapper { get; set; }
@ -115,6 +117,7 @@ namespace PlexRequests.UI.Tests
LandingPageSettings = new Mock<ISettingsService<LandingPageSettings>>(); LandingPageSettings = new Mock<ISettingsService<LandingPageSettings>>();
ScheduledJobsSettingsMock = new Mock<ISettingsService<ScheduledJobsSettings>>(); ScheduledJobsSettingsMock = new Mock<ISettingsService<ScheduledJobsSettings>>();
RecorderMock = new Mock<IJobRecord>(); RecorderMock = new Mock<IJobRecord>();
IAnalytics = new Mock<IAnalytics>();
Bootstrapper = new ConfigurableBootstrapper(with => Bootstrapper = new ConfigurableBootstrapper(with =>
@ -136,6 +139,7 @@ namespace PlexRequests.UI.Tests
with.Dependency(PushoverSettings.Object); with.Dependency(PushoverSettings.Object);
with.Dependency(PushoverApi.Object); with.Dependency(PushoverApi.Object);
with.Dependency(NotificationService.Object); with.Dependency(NotificationService.Object);
with.Dependency(IAnalytics.Object);
with.Dependency(HeadphonesSettings.Object); with.Dependency(HeadphonesSettings.Object);
with.Dependency(Cache.Object); with.Dependency(Cache.Object);
with.Dependency(Log.Object); with.Dependency(Log.Object);

View file

@ -1,149 +0,0 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: IssuesModuleTests.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.Linq;
using System.Threading.Tasks;
using Moq;
using Nancy;
using Nancy.Testing;
using Newtonsoft.Json;
using NUnit.Framework;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Services.Interfaces;
using PlexRequests.UI.Models;
using PlexRequests.UI.Modules;
using Ploeh.AutoFixture;
namespace PlexRequests.UI.Tests
{
[TestFixture]
public class IssuesModuleTests
{
[SetUp]
public void Setup()
{
var f = new Fixture();
ModelList = f.CreateMany<IssuesModel>();
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings()));
IssueServiceMock = new Mock<IIssueService>();
RequestServiceMock = new Mock<IRequestService>();
NotificationServiceMock = new Mock<INotificationService>();
IssueServiceMock.Setup(x => x.GetAllAsync()).Returns(Task.FromResult(ModelList));
Bootstrapper = new ConfigurableBootstrapper(
with =>
{
with.Module<IssuesModule>();
with.Dependency(PlexRequestMock.Object);
with.Dependency(IssueServiceMock.Object);
with.Dependency(RequestServiceMock.Object);
with.Dependency(NotificationServiceMock.Object);
with.RootPathProvider<TestRootPathProvider>();
});
Bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
}
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
private Mock<IIssueService> IssueServiceMock { get; set; }
private Mock<IRequestService> RequestServiceMock { get; set; }
private Mock<INotificationService> NotificationServiceMock { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
private IEnumerable<IssuesModel> ModelList { get; set; }
private IEnumerable<IssuesModel> NonResolvedModel => ModelList.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue);
[Test]
public void GetIssuesNonAdminButAllCanSee()
{
var browser = new Browser(Bootstrapper);
var result = browser.Get(
"/issues/pending",
with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<List<IssuesViewModel>>(result.Body.AsString());
Assert.That(body.Count, Is.EqualTo(NonResolvedModel.Count()));
Assert.That(body[0].Title, Is.Not.Empty);
}
[Test]
public void GetIssuesForAdmin()
{
Bootstrapper = new ConfigurableBootstrapper(
with =>
{
with.Module<IssuesModule>();
with.Dependency(PlexRequestMock.Object);
with.Dependency(IssueServiceMock.Object);
with.Dependency(RequestServiceMock.Object);
with.Dependency(NotificationServiceMock.Object);
with.RootPathProvider<TestRootPathProvider>();
with.RequestStartup(
(container, pipelines, context) =>
{
context.CurrentUser = new UserIdentity() { Claims = new[] { UserClaims.Admin } };
});
});
Bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
var browser = new Browser(Bootstrapper);
var result = browser.Get(
"/issues/pending",
with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<List<IssuesViewModel>>(result.Body.AsString());
Assert.That(body.Count, Is.EqualTo(NonResolvedModel.Count()));
Assert.That(body[0].Title, Is.Not.Empty);
}
}
}

View file

@ -105,7 +105,6 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringHelperTests.cs" /> <Compile Include="StringHelperTests.cs" />
<Compile Include="TestRootPathProvider.cs" /> <Compile Include="TestRootPathProvider.cs" />
<Compile Include="IssuesModuleTests.cs" />
<Compile Include="UserLoginModuleTests.cs" /> <Compile Include="UserLoginModuleTests.cs" />
<Compile Include="AdminModuleTests.cs" /> <Compile Include="AdminModuleTests.cs" />
</ItemGroup> </ItemGroup>

View file

@ -40,6 +40,7 @@ using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex; using PlexRequests.Api.Models.Plex;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
using PlexRequests.UI.Modules; using PlexRequests.UI.Modules;
@ -53,6 +54,7 @@ namespace PlexRequests.UI.Tests
private Mock<ISettingsService<LandingPageSettings>> LandingPageMock { get; set; } private Mock<ISettingsService<LandingPageSettings>> LandingPageMock { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; } private ConfigurableBootstrapper Bootstrapper { get; set; }
private Mock<IPlexApi> PlexMock { get; set; } private Mock<IPlexApi> PlexMock { get; set; }
private Mock<IAnalytics> IAnalytics { get; set; }
[SetUp] [SetUp]
public void Setup() public void Setup()
@ -64,6 +66,7 @@ namespace PlexRequests.UI.Tests
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings())); PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings()));
LandingPageMock.Setup(x => x.GetSettings()).Returns(new LandingPageSettings()); LandingPageMock.Setup(x => x.GetSettings()).Returns(new LandingPageSettings());
IAnalytics = new Mock<IAnalytics>();
Bootstrapper = new ConfigurableBootstrapper(with => Bootstrapper = new ConfigurableBootstrapper(with =>
{ {
with.Module<UserLoginModule>(); with.Module<UserLoginModule>();
@ -71,6 +74,7 @@ namespace PlexRequests.UI.Tests
with.Dependency(AuthMock.Object); with.Dependency(AuthMock.Object);
with.Dependency(PlexMock.Object); with.Dependency(PlexMock.Object);
with.Dependency(LandingPageMock.Object); with.Dependency(LandingPageMock.Object);
with.Dependency(IAnalytics.Object);
with.RootPathProvider<TestRootPathProvider>(); with.RootPathProvider<TestRootPathProvider>();
}); });
} }

View file

@ -43,6 +43,7 @@ using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Services;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification; using PlexRequests.Services.Notification;
using PlexRequests.Store; using PlexRequests.Store;
@ -167,33 +168,33 @@ namespace PlexRequests.UI
container.Register<IRepository<UsersModel>, UserRepository<UsersModel>>(); container.Register<IRepository<UsersModel>, UserRepository<UsersModel>>();
container.Register<IUserMapper, UserMapper>(); container.Register<IUserMapper, UserMapper>();
container.Register<ICustomUserMapper, UserMapper>(); container.Register<ICustomUserMapper, UserMapper>();
container.Register<ISettingsService<EmailNotificationSettings>, SettingsServiceV2<EmailNotificationSettings>>();
// Settings container.Register<ISettingsService<PushbulletNotificationSettings>, SettingsServiceV2<PushbulletNotificationSettings>>();
container.RegisterSetting<EmailNotificationSettings>(); container.Register<ISettingsService<PushoverNotificationSettings>, SettingsServiceV2<PushoverNotificationSettings>>();
container.RegisterSetting<PushbulletNotificationSettings>(); container.Register<ISettingsService<SlackNotificationSettings>, SettingsServiceV2<SlackNotificationSettings>>();
container.RegisterSetting<PushoverNotificationSettings>(); container.Register<ISettingsService<ScheduledJobsSettings>, SettingsServiceV2<ScheduledJobsSettings>>();
container.RegisterSetting<SlackNotificationSettings>();
container.RegisterSetting<ScheduledJobsSettings>();
container.RegisterSetting<PlexRequestSettings>();
container.RegisterSetting<CouchPotatoSettings>();
container.RegisterSetting<AuthenticationSettings>();
container.RegisterSetting<PlexSettings>();
container.RegisterSetting<SonarrSettings>();
container.RegisterSetting<SickRageSettings>();
container.RegisterSetting<HeadphonesSettings>();
container.RegisterSetting<LogSettings>();
container.RegisterSetting<LandingPageSettings>();
// Repository
container.RegisterRepo<LogEntity>();
container.RegisterRepo<UsersToNotify>();
container.RegisterRepo<ScheduledJobs>();
container.RegisterRepo<IssueBlobs>();
// Notification Service // Notification Service
container.Register<INotificationService, NotificationService>().AsSingleton(); container.Register<INotificationService, NotificationService>().AsSingleton();
// 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<HeadphonesSettings>, SettingsServiceV2<HeadphonesSettings>>();
container.Register<ISettingsService<LogSettings>, SettingsServiceV2<LogSettings>>();
container.Register<ISettingsService<LandingPageSettings>, SettingsServiceV2<LandingPageSettings>>();
// Repo's
container.Register<IRepository<LogEntity>, GenericRepository<LogEntity>>();
container.Register<IRepository<UsersToNotify>, GenericRepository<UsersToNotify>>();
container.Register<IRepository<ScheduledJobs>, GenericRepository<ScheduledJobs>>();
container.Register<IRepository<IssueBlobs>, GenericRepository<IssueBlobs>>();
container.Register<IRepository<RequestLimit>, GenericRepository<RequestLimit>>();
container.Register<IRepository<PlexUsers>, GenericRepository<PlexUsers>>();
container.Register<IRequestService, JsonRequestModelRequestService>(); container.Register<IRequestService, JsonRequestModelRequestService>();
container.Register<IIssueService, IssueJsonService>(); container.Register<IIssueService, IssueJsonService>();
container.Register<ISettingsRepository, SettingsJsonRepository>(); container.Register<ISettingsRepository, SettingsJsonRepository>();

View file

@ -28,19 +28,43 @@ $(".theNoteSaveButton").click(function (e) {
}); });
// Update the note modal // Update the note modal
$('#noteModal').on('show.bs.modal', function (event) { $('#noteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal var button = $(event.relatedTarget); // Button that triggered the modal
var id = button.data('identifier'); // Extract info from data-* attributes var id = button.data('identifier'); // Extract info from data-* attributes
var issue = button.data('issue'); var issue = button.data('issue');
var modal = $(this); var modal = $(this);
modal.find('.theNoteSaveButton').val(id); // Add ID to the button modal.find('.theNoteSaveButton').val(id); // Add ID to the button
var requestField = modal.find('.noteId'); var requestField = modal.find('.noteId');
requestField.val(id); // Add ID to the hidden field requestField.val(id); // Add ID to the hidden field
var noteType = modal.find('.issue'); var noteType = modal.find('.issue');
noteType.val(issue); noteType.val(issue);
}); });
$('.delete').click(function(e) {
e.preventDefault();
var url = createBaseUrl(base, "/issues");
var $form = $("#removeForm");
$.ajax({
type: $form.prop("method"),
url: $form.prop("action"),
data: $form.serialize(),
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) { window.location.replace(url); }
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});

View file

@ -264,8 +264,9 @@ function buildIssueContext(result) {
requestId: result.requestId, requestId: result.requestId,
type: result.type, type: result.type,
title: result.title, title: result.title,
issues: result.issues issues: result.issues,
}; admin: result.admin
};
return context; return context;
} }

View file

@ -159,7 +159,7 @@ $('#approveTVShows').click(function (e) {
$('#deleteMovies').click(function (e) { $('#deleteMovies').click(function (e) {
e.preventDefault(); e.preventDefault();
if (!confirm("Are you sure you want to delete all TV show requests?")) return; if (!confirm("Are you sure you want to delete all Movie requests?")) return;
var buttonId = e.target.id; var buttonId = e.target.id;
var origHtml = $(this).html(); var origHtml = $(this).html();

View file

@ -0,0 +1,117 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CultureHelper.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 System.Linq;
using System.Threading;
namespace PlexRequests.UI.Helpers
{
public class CultureHelper
{
private static readonly List<string> ValidCultures = new List<string> { "en-US", "de-DE", "fr-FR", "es-ES", "de", "en", "fr", "es","da","sv","it","nl" };
private static readonly List<string> ImplimentedCultures = new List<string> {
"en-US",
"en",
"de",
"fr",
"es",
"da",
"sv",
"it",
"nl"
};
/// <summary>
/// Returns true if the language is a right-to-left language.
/// </summary>
public static bool IsRighToLeft()
{
return Thread.CurrentThread.CurrentCulture.TextInfo.IsRightToLeft;
}
/// <summary>
/// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US"
/// </summary>
/// <param name='name'> Culture's name (e.g. en-US) </param>
public static string GetImplementedCulture(string name)
{
if (string.IsNullOrEmpty(name))
{
return GetDefaultCulture();
}
// Validate the culture
if (!ValidCultures.Any(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)))
{
return GetDefaultCulture();
}
if (ImplimentedCultures.Any(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)))
{
return name; // We do have the culture, lets go with it
}
// Find a close match.
var match = GetNeutralCulture(name);
foreach (var c in ImplimentedCultures.Where(c => c.StartsWith(match, StringComparison.InvariantCultureIgnoreCase)))
{
return c;
}
return GetDefaultCulture(); // return Default culture since we cannot find anything
}
/// <summary>
/// Returns default culture name which is the first name declared (e.g. en-US)
/// </summary>
/// <returns> Culture string e.g. en-US </returns>
public static string GetDefaultCulture()
{
return ImplimentedCultures[0]; // return first culture
}
public static string GetCurrentCulture()
{
return Thread.CurrentThread.CurrentCulture.Name;
}
public static string GetCurrentNeutralCulture()
{
return GetNeutralCulture(Thread.CurrentThread.CurrentCulture.Name);
}
public static string GetNeutralCulture(string name)
{
if (!name.Contains("-")) return name;
return name.Split('-')[0]; // Read first part only
}
}
}

View file

@ -53,15 +53,16 @@ namespace PlexRequests.UI.Helpers
public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId) public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId)
{ {
var qualityProfile = 0; var qualityProfile = 0;
if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality
{ {
if (!int.TryParse(qualityId, out qualityProfile)) int.TryParse(qualityId, out qualityProfile);
{ }
int.TryParse(sonarrSettings.QualityProfile, out qualityProfile);
} if (qualityProfile <= 0)
{
int.TryParse(sonarrSettings.QualityProfile, out qualityProfile);
} }
var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,

View file

@ -62,6 +62,7 @@ namespace PlexRequests.UI.Jobs
var cp = JobBuilder.Create<CouchPotatoCacher>().WithIdentity("CouchPotatoCacher", "Cache").Build(); var cp = JobBuilder.Create<CouchPotatoCacher>().WithIdentity("CouchPotatoCacher", "Cache").Build();
var store = JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Database").Build(); var store = JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Database").Build();
var storeClean = JobBuilder.Create<StoreCleanup>().WithIdentity("StoreCleanup", "Database").Build(); var storeClean = JobBuilder.Create<StoreCleanup>().WithIdentity("StoreCleanup", "Database").Build();
var userRequestLimitReset = JobBuilder.Create<UserRequestLimitResetter>().WithIdentity("UserRequestLimiter", "Request").Build();
jobs.Add(plex); jobs.Add(plex);
jobs.Add(sickrage); jobs.Add(sickrage);
@ -69,6 +70,7 @@ namespace PlexRequests.UI.Jobs
jobs.Add(cp); jobs.Add(cp);
jobs.Add(store); jobs.Add(store);
jobs.Add(storeClean); jobs.Add(storeClean);
jobs.Add(userRequestLimitReset);
return jobs; return jobs;
} }
@ -150,6 +152,13 @@ namespace PlexRequests.UI.Jobs
.WithSimpleSchedule(x => x.WithIntervalInHours(s.StoreCleanup).RepeatForever()) .WithSimpleSchedule(x => x.WithIntervalInHours(s.StoreCleanup).RepeatForever())
.Build(); .Build();
var userRequestLimiter =
TriggerBuilder.Create()
.WithIdentity("UserRequestLimiter", "Request")
.StartAt(DateTimeOffset.Now.AddMinutes(5)) // Everything has started on application start, lets wait 5 minutes
.WithSimpleSchedule(x => x.WithIntervalInHours(s.UserRequestLimitResetter).RepeatForever())
.Build();
triggers.Add(plexAvailabilityChecker); triggers.Add(plexAvailabilityChecker);
triggers.Add(srCacher); triggers.Add(srCacher);
@ -157,6 +166,7 @@ namespace PlexRequests.UI.Jobs
triggers.Add(cpCacher); triggers.Add(cpCacher);
triggers.Add(storeBackup); triggers.Add(storeBackup);
triggers.Add(storeCleanup); triggers.Add(storeCleanup);
triggers.Add(userRequestLimiter);
return triggers; return triggers;
} }

View file

@ -36,6 +36,7 @@ namespace PlexRequests.UI.Models
public string Title { get; set; } public string Title { get; set; }
public string Issues { get; set; } public string Issues { get; set; }
public string Type { get; set; } public string Type { get; set; }
public bool Admin { get; set; }
} }
} }

View file

@ -32,7 +32,6 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -47,11 +46,15 @@ using NLog;
using MarkdownSharp; using MarkdownSharp;
using Nancy.Responses;
using PlexRequests.Api; using PlexRequests.Api;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
using PlexRequests.Helpers.Exceptions; using PlexRequests.Helpers.Exceptions;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification; using PlexRequests.Services.Notification;
@ -60,6 +63,7 @@ using PlexRequests.Store.Repository;
using PlexRequests.UI.Helpers; using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
using Action = PlexRequests.Helpers.Analytics.Action;
namespace PlexRequests.UI.Modules namespace PlexRequests.UI.Modules
{ {
@ -89,6 +93,7 @@ namespace PlexRequests.UI.Modules
private ISettingsService<ScheduledJobsSettings> ScheduledJobSettings { get; } private ISettingsService<ScheduledJobsSettings> ScheduledJobSettings { get; }
private ISlackApi SlackApi { get; } private ISlackApi SlackApi { get; }
private IJobRecord JobRecorder { get; } private IJobRecord JobRecorder { get; }
private IAnalytics Analytics { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
public AdminModule(ISettingsService<PlexRequestSettings> prService, public AdminModule(ISettingsService<PlexRequestSettings> prService,
@ -111,7 +116,7 @@ namespace PlexRequests.UI.Modules
ISettingsService<LogSettings> logs, ISettingsService<LogSettings> logs,
ICacheProvider cache, ISettingsService<SlackNotificationSettings> slackSettings, ICacheProvider cache, ISettingsService<SlackNotificationSettings> slackSettings,
ISlackApi slackApi, ISettingsService<LandingPageSettings> lp, ISlackApi slackApi, ISettingsService<LandingPageSettings> lp,
ISettingsService<ScheduledJobsSettings> scheduler, IJobRecord rec) : base("admin", prService) ISettingsService<ScheduledJobsSettings> scheduler, IJobRecord rec, IAnalytics analytics) : base("admin", prService)
{ {
PrService = prService; PrService = prService;
CpService = cpService; CpService = cpService;
@ -137,6 +142,7 @@ namespace PlexRequests.UI.Modules
LandingSettings = lp; LandingSettings = lp;
ScheduledJobSettings = scheduler; ScheduledJobSettings = scheduler;
JobRecorder = rec; JobRecorder = rec;
Analytics = analytics;
this.RequiresClaims(UserClaims.Admin); this.RequiresClaims(UserClaims.Admin);
@ -145,7 +151,7 @@ namespace PlexRequests.UI.Modules
Get["/authentication", true] = async (x, ct) => await Authentication(); Get["/authentication", true] = async (x, ct) => await Authentication();
Post["/authentication", true] = async (x, ct) => await SaveAuthentication(); Post["/authentication", true] = async (x, ct) => await SaveAuthentication();
Post["/"] = _ => SaveAdmin(); Post["/", true] = async (x, ct) => await SaveAdmin();
Post["/requestauth"] = _ => RequestAuthToken(); Post["/requestauth"] = _ => RequestAuthToken();
@ -201,6 +207,8 @@ namespace PlexRequests.UI.Modules
Get["/scheduledjobs", true] = async (x, ct) => await GetScheduledJobs(); Get["/scheduledjobs", true] = async (x, ct) => await GetScheduledJobs();
Post["/scheduledjobs", true] = async (x, ct) => await SaveScheduledJobs(); Post["/scheduledjobs", true] = async (x, ct) => await SaveScheduledJobs();
Post["/clearlogs", true] = async (x, ct) => await ClearLogs();
} }
private async Task<Negotiator> Authentication() private async Task<Negotiator> Authentication()
@ -237,7 +245,7 @@ namespace PlexRequests.UI.Modules
return View["Settings", settings]; return View["Settings", settings];
} }
private Response SaveAdmin() private async Task<Response> SaveAdmin()
{ {
var model = this.Bind<PlexRequestSettings>(); var model = this.Bind<PlexRequestSettings>();
var valid = this.Validate(model); var valid = this.Validate(model);
@ -253,7 +261,13 @@ namespace PlexRequests.UI.Modules
model.BaseUrl = model.BaseUrl.Remove(0, 1); model.BaseUrl = model.BaseUrl.Remove(0, 1);
} }
} }
if (!model.CollectAnalyticData)
{
Analytics.TrackEventAsync(Category.Admin, Action.Save, "CollectAnalyticData turned off", Username, CookieHelper.GetAnalyticClientId(Cookies));
}
var result = PrService.SaveSettings(model); var result = PrService.SaveSettings(model);
Analytics.TrackEventAsync(Category.Admin, Action.Save, "PlexRequestSettings", Username, CookieHelper.GetAnalyticClientId(Cookies));
return Response.AsJson(result return Response.AsJson(result
? new JsonResponseModel { Result = true } ? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" });
@ -818,7 +832,14 @@ namespace PlexRequests.UI.Modules
private async Task<Negotiator> LandingPage() private async Task<Negotiator> LandingPage()
{ {
var settings = await LandingSettings.GetSettingsAsync(); var settings = await LandingSettings.GetSettingsAsync();
if (settings.NoticeEnd == DateTime.MinValue)
{
settings.NoticeEnd = DateTime.Now;
}
if (settings.NoticeStart == DateTime.MinValue)
{
settings.NoticeStart = DateTime.Now;
}
return View["LandingPage", settings]; return View["LandingPage", settings];
} }
@ -872,5 +893,23 @@ namespace PlexRequests.UI.Modules
? new JsonResponseModel { Result = true } ? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" });
} }
private async Task<Response> ClearLogs()
{
try
{
var allLogs = await LogsRepo.GetAllAsync();
foreach (var logEntity in allLogs)
{
await LogsRepo.DeleteAsync(logEntity);
}
return Response.AsJson(new JsonResponseModel { Result = true });
}
catch (Exception e)
{
Log.Error(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message });
}
}
} }
} }

View file

@ -25,13 +25,16 @@
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using Nancy; using Nancy;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules namespace PlexRequests.UI.Modules
@ -40,8 +43,10 @@ namespace PlexRequests.UI.Modules
{ {
protected string BaseUrl { get; set; } protected string BaseUrl { get; set; }
protected BaseModule(ISettingsService<PlexRequestSettings> settingsService) protected BaseModule(ISettingsService<PlexRequestSettings> settingsService)
{ {
var settings = settingsService.GetSettings(); var settings = settingsService.GetSettings();
var baseUrl = settings.BaseUrl; var baseUrl = settings.BaseUrl;
BaseUrl = baseUrl; BaseUrl = baseUrl;
@ -49,10 +54,13 @@ namespace PlexRequests.UI.Modules
var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl; var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl;
ModulePath = modulePath; ModulePath = modulePath;
Before += (ctx) => SetCookie();
} }
protected BaseModule(string modulePath, ISettingsService<PlexRequestSettings> settingsService) protected BaseModule(string modulePath, ISettingsService<PlexRequestSettings> settingsService)
{ {
var settings = settingsService.GetSettings(); var settings = settingsService.GetSettings();
var baseUrl = settings.BaseUrl; var baseUrl = settings.BaseUrl;
BaseUrl = baseUrl; BaseUrl = baseUrl;
@ -60,6 +68,8 @@ namespace PlexRequests.UI.Modules
var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}"; var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}";
ModulePath = settingModulePath; ModulePath = settingModulePath;
Before += (ctx) => SetCookie();
} }
private int _dateTimeOffset = -1; private int _dateTimeOffset = -1;
@ -82,12 +92,21 @@ namespace PlexRequests.UI.Modules
{ {
if (string.IsNullOrEmpty(_username)) if (string.IsNullOrEmpty(_username))
{ {
_username = Session[SessionKeys.UsernameKey].ToString(); try
{
_username = Session[SessionKeys.UsernameKey].ToString();
}
catch (Exception)
{
return string.Empty;
}
} }
return _username; return _username;
} }
} }
protected IDictionary<string, string> Cookies => Request?.Cookies;
protected bool IsAdmin protected bool IsAdmin
{ {
get get
@ -100,6 +119,41 @@ namespace PlexRequests.UI.Modules
return claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser); return claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser);
} }
} }
protected string Culture { get; set; }
protected const string CultureCookieName = "_culture";
protected Response SetCookie()
{
try
{
string cultureName;
// Attempt to read the culture cookie from Request
var outCookie = string.Empty;
if (Cookies.TryGetValue(CultureCookieName, out outCookie))
{
cultureName = outCookie;
}
else
{
cultureName = Request.Headers?.AcceptLanguage?.FirstOrDefault()?.Item1;
}
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
Culture = Thread.CurrentThread.CurrentCulture.Name;
}
catch (Exception)
{
// Couldn't Set the culture
}
return null;
}
} }
} }

View file

@ -0,0 +1,79 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CultureModule.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.Threading.Tasks;
using Nancy;
using Nancy.Extensions;
using Nancy.Responses;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Helpers;
namespace PlexRequests.UI.Modules
{
public class CultureModule : BaseModule
{
public CultureModule(ISettingsService<PlexRequestSettings> pr, IAnalytics a) : base("culture",pr)
{
Analytics = a;
Get["/", true] = async(x,c) => await SetCulture();
}
private IAnalytics Analytics { get; }
public async Task<RedirectResponse> SetCulture()
{
var culture = (string)Request.Query["l"];
var returnUrl = (string)Request.Query["u"];
// Validate
culture = CultureHelper.GetImplementedCulture(culture);
var outCookie = string.Empty;
if (Cookies.TryGetValue(CultureCookieName, out outCookie))
{
Cookies[CultureCookieName] = culture;
}
else
{
Cookies.Add(CultureCookieName, culture);
}
var cookie = Cookies[CultureCookieName];
var response = Context.GetRedirect(returnUrl);
response.WithCookie(CultureCookieName, cookie ?? culture, DateTime.Now.AddYears(1));
Analytics.TrackEventAsync(Category.Navbar, PlexRequests.Helpers.Analytics.Action.Language, culture, Username, CookieHelper.GetAnalyticClientId(Cookies));
return response;
}
}
}

View file

@ -48,7 +48,7 @@ namespace PlexRequests.UI.Modules
Get["/issuecount", true] = async (x, ct) => await IssueCount(); Get["/issuecount", true] = async (x, ct) => await IssueCount();
Get["/tabCount", true] = async (x, ct) => await TabCount(); Get["/tabCount", true] = async (x, ct) => await TabCount();
Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.provierId, IssueState.Other, (string)Request.Form.commentArea); Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.providerId, IssueState.Other, (string)Request.Form.commentArea);
Post["/nonrequestissue", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, (IssueState)(int)Request.Form.issue, null); Post["/nonrequestissue", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, (IssueState)(int)Request.Form.issue, null);
@ -78,7 +78,7 @@ namespace PlexRequests.UI.Modules
foreach (var i in issuesModels) foreach (var i in issuesModels)
{ {
var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), }; var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), Admin = IsAdmin };
// Create a string with all of the current issue states with a "," delimiter in e.g. Wrong Content, Playback Issues // Create a string with all of the current issue states with a "," delimiter in e.g. Wrong Content, Playback Issues
var state = i.Issues.Select(x => x.Issue).ToArray(); var state = i.Issues.Select(x => x.Issue).ToArray();
@ -317,8 +317,6 @@ namespace PlexRequests.UI.Modules
/// Filters the issues. Checks to see if we have set <c>UsersCanViewOnlyOwnIssues</c> in the database and filters upon the user logged in and that setting. /// Filters the issues. Checks to see if we have set <c>UsersCanViewOnlyOwnIssues</c> in the database and filters upon the user logged in and that setting.
/// </summary> /// </summary>
/// <param name="issues">The issues.</param> /// <param name="issues">The issues.</param>
/// <param name="showResolved">if set to <c>true</c> [show resolved].</param>
/// <returns></returns>
private async Task<IEnumerable<IssuesModel>> FilterIssuesAsync(IEnumerable<IssuesModel> issues, bool showResolved = false) private async Task<IEnumerable<IssuesModel>> FilterIssuesAsync(IEnumerable<IssuesModel> issues, bool showResolved = false)
{ {
var settings = await PlexRequestSettings.GetSettingsAsync(); var settings = await PlexRequestSettings.GetSettingsAsync();
@ -364,29 +362,35 @@ namespace PlexRequests.UI.Modules
return myIssues; return myIssues;
} }
private async Task<Negotiator> RemoveIssue(int issueId) private async Task<Response> RemoveIssue(int issueId)
{ {
try try
{ {
this.RequiresClaims(UserClaims.Admin); this.RequiresClaims(UserClaims.Admin);
var issue = await IssuesService.GetAsync(issueId); var issue = await IssuesService.GetAsync(issueId);
var request = await RequestService.GetAsync(issue.RequestId); var request = await RequestService.GetAsync(issue.RequestId);
if (request.Id > 0)
{
request.IssueId = 0; // No issue;
request.IssueId = 0; // No issue; var result = await RequestService.UpdateRequestAsync(request);
if (result)
var result = await RequestService.UpdateRequestAsync(request); {
if (result) await IssuesService.DeleteIssueAsync(issueId);
}
}
else
{ {
await IssuesService.DeleteIssueAsync(issueId); await IssuesService.DeleteIssueAsync(issueId);
} }
return View["Index"]; return Response.AsJson(new JsonResponseModel { Result = true });
} }
catch (Exception e) catch (Exception e)
{ {
Log.Error(e); Log.Error(e);
return View["Index"]; return Response.AsJson(new JsonResponseModel() { Result = false, Message = "Could not delete issue! Check the logs."});
} }
} }

View file

@ -58,6 +58,7 @@ namespace PlexRequests.UI.Modules
private async Task<Negotiator> Index() private async Task<Negotiator> Index()
{ {
var s = await LandingSettings.GetSettingsAsync(); var s = await LandingSettings.GetSettingsAsync();
var model = new LandingPageViewModel var model = new LandingPageViewModel
{ {

View file

@ -0,0 +1,98 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexUsersModule.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.Responses.Negotiation;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Music;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
{
public class PlexUsersModule : BaseAuthModule
{
public PlexUsersModule(ISettingsService<PlexRequestSettings> pr, IPlexApi plexApi, ISettingsService<AuthenticationSettings> auth,
IRepository<PlexUsers> repo) : base("plexusers", pr)
{
PlexApi = plexApi;
AuthSettings = auth;
Repo = repo;
Get["/"] = x => Index();
Get["/users", true] = async (x, ct) => await GetPlexUsers();
Post["/alias", true] = async (x, ct) => await Alias();
}
private IPlexApi PlexApi { get; }
private ISettingsService<AuthenticationSettings> AuthSettings { get; }
private IRepository<PlexUsers> Repo { get; }
private Negotiator Index()
{
return View["Index"];
}
private async Task<Response> GetPlexUsers()
{
var authSettings = await AuthSettings.GetSettingsAsync();
var users = PlexApi.GetUsers(authSettings.PlexAuthToken);
return Response.AsJson(users.User);
}
private async Task<Response> Alias()
{
var plexUserId = (int)Request.Form["plexid"];
var alias = (string)Request.Form["alias"];
var allUsers = await Repo.GetAllAsync();
var existingUser = allUsers.FirstOrDefault(x => x.PlexUserId == plexUserId);
if (existingUser == null)
{
// Create a new mapping
existingUser = new PlexUsers { PlexUserId = plexUserId, UserAlias = alias };
}
else
{
// Modify existing alias
existingUser.UserAlias = alias;
}
await Repo.InsertAsync(existingUser);
return Response.AsJson(new JsonResponseModel { Result = true });
}
}
}

View file

@ -46,6 +46,8 @@ using System.Threading.Tasks;
using NLog; using NLog;
using PlexRequests.Core.Models;
namespace PlexRequests.UI.Modules namespace PlexRequests.UI.Modules
{ {
public class RequestsModule : BaseAuthModule public class RequestsModule : BaseAuthModule

View file

@ -53,11 +53,14 @@ using Nancy.Responses;
using PlexRequests.Api.Models.Tv; using PlexRequests.Api.Models.Tv;
using PlexRequests.Core.Models; using PlexRequests.Core.Models;
using PlexRequests.Helpers.Analytics;
using PlexRequests.Store.Models; using PlexRequests.Store.Models;
using PlexRequests.Store.Repository; using PlexRequests.Store.Repository;
using TMDbLib.Objects.General; using TMDbLib.Objects.General;
using Action = PlexRequests.Helpers.Analytics.Action;
namespace PlexRequests.UI.Modules namespace PlexRequests.UI.Modules
{ {
public class SearchModule : BaseAuthModule public class SearchModule : BaseAuthModule
@ -69,7 +72,7 @@ namespace PlexRequests.UI.Modules
INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService<HeadphonesSettings> hpService, INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService<HeadphonesSettings> hpService,
ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi,
ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth, IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email, ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth, IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email,
IIssueService issue) : base("search", prSettings) IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl) : base("search", prSettings)
{ {
Auth = auth; Auth = auth;
PlexService = plexService; PlexService = plexService;
@ -95,6 +98,8 @@ namespace PlexRequests.UI.Modules
UsersToNotifyRepo = u; UsersToNotifyRepo = u;
EmailNotificationSettings = email; EmailNotificationSettings = email;
IssueService = issue; IssueService = issue;
Analytics = a;
RequestLimitRepo = rl;
Get["/", true] = async (x, ct) => await RequestLoad(); Get["/", true] = async (x, ct) => await RequestLoad();
@ -140,31 +145,32 @@ namespace PlexRequests.UI.Modules
private IHeadphonesApi HeadphonesApi { get; } private IHeadphonesApi HeadphonesApi { get; }
private IRepository<UsersToNotify> UsersToNotifyRepo { get; } private IRepository<UsersToNotify> UsersToNotifyRepo { get; }
private IIssueService IssueService { get; } private IIssueService IssueService { get; }
private IAnalytics Analytics { get; }
private IRepository<RequestLimit> RequestLimitRepo { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private async Task<Negotiator> RequestLoad() private async Task<Negotiator> RequestLoad()
{ {
var settings = await PrService.GetSettingsAsync(); var settings = await PrService.GetSettingsAsync();
Log.Trace("Loading Index");
return View["Search/Index", settings]; return View["Search/Index", settings];
} }
private async Task<Response> UpcomingMovies() private async Task<Response> UpcomingMovies()
{ {
Log.Trace("Loading upcoming movies"); Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, CookieHelper.GetAnalyticClientId(Cookies));
return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); return await ProcessMovies(MovieSearchType.Upcoming, string.Empty);
} }
private async Task<Response> CurrentlyPlayingMovies() private async Task<Response> CurrentlyPlayingMovies()
{ {
Log.Trace("Loading currently playing movies"); Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, CookieHelper.GetAnalyticClientId(Cookies));
return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty);
} }
private async Task<Response> SearchMovie(string searchTerm) private async Task<Response> SearchMovie(string searchTerm)
{ {
Log.Trace("Searching for Movie {0}", searchTerm); Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies));
return await ProcessMovies(MovieSearchType.Search, searchTerm); return await ProcessMovies(MovieSearchType.Search, searchTerm);
} }
@ -279,6 +285,7 @@ namespace PlexRequests.UI.Modules
private async Task<Response> SearchTvShow(string searchTerm) private async Task<Response> SearchTvShow(string searchTerm)
{ {
Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies));
var plexSettings = await PlexService.GetSettingsAsync(); var plexSettings = await PlexService.GetSettingsAsync();
Log.Trace("Searching for TV Show {0}", searchTerm); Log.Trace("Searching for TV Show {0}", searchTerm);
@ -366,6 +373,7 @@ namespace PlexRequests.UI.Modules
private async Task<Response> SearchMusic(string searchTerm) private async Task<Response> SearchMusic(string searchTerm)
{ {
Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies));
var apiAlbums = new List<Release>(); var apiAlbums = new List<Release>();
await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) =>
{ {
@ -417,12 +425,17 @@ namespace PlexRequests.UI.Modules
private async Task<Response> RequestMovie(int movieId) private async Task<Response> RequestMovie(int movieId)
{ {
var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.Movie))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for Movies! Please contact your admin." });
}
Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, CookieHelper.GetAnalyticClientId(Cookies));
var movieInfo = MovieApi.GetMovieInformation(movieId).Result; var movieInfo = MovieApi.GetMovieInformation(movieId).Result;
var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}";
Log.Trace("Getting movie info from TheMovieDb"); Log.Trace("Getting movie info from TheMovieDb");
var settings = await PrService.GetSettingsAsync();
// check if the movie has already been requested // check if the movie has already been requested
Log.Info("Requesting movie with id {0}", movieId); Log.Info("Requesting movie with id {0}", movieId);
var existingRequest = await RequestService.CheckRequestAsync(movieId); var existingRequest = await RequestService.CheckRequestAsync(movieId);
@ -484,62 +497,23 @@ namespace PlexRequests.UI.Modules
Log.Debug("Adding movie to CP result {0}", result); Log.Debug("Adding movie to CP result {0}", result);
if (result) if (result)
{ {
model.Approved = true; return await AddRequest(model, settings, $"{fullMovieName} was successfully added!");
Log.Info("Adding movie to database (No approval required)");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
var notificationModel = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
await NotificationService.Publish(notificationModel);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
} }
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message =
"Something went wrong adding the movie to CouchPotato! Please check your settings."
});
}
else
{
model.Approved = true;
Log.Info("Adding movie to database (No approval required)");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification()) return Response.AsJson(new JsonResponseModel
{ {
var notificationModel = new NotificationModel Result = false,
{ Message =
Title = model.Title, "Something went wrong adding the movie to CouchPotato! Please check your settings."
User = Username, });
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
await NotificationService.Publish(notificationModel);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
} }
return await AddRequest(model, settings, $"{fullMovieName} was successfully added!");
} }
try try
{ {
Log.Info("Adding movie to database"); return await AddRequest(model, settings, $"{fullMovieName} was successfully added!");
var id = await RequestService.AddRequestAsync(model);
var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest };
await NotificationService.Publish(notificationModel);
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
} }
catch (Exception e) catch (Exception e)
{ {
@ -557,6 +531,12 @@ namespace PlexRequests.UI.Modules
/// <returns></returns> /// <returns></returns>
private async Task<Response> RequestTvShow(int showId, string seasons) private async Task<Response> RequestTvShow(int showId, string seasons)
{ {
var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.TvShow))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for TV Shows! Please contact your admin." });
}
Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, CookieHelper.GetAnalyticClientId(Cookies));
var tvApi = new TvMazeApi(); var tvApi = new TvMazeApi();
var showInfo = tvApi.ShowLookupByTheTvDbId(showId); var showInfo = tvApi.ShowLookupByTheTvDbId(showId);
@ -565,7 +545,6 @@ namespace PlexRequests.UI.Modules
string fullShowName = $"{showInfo.name} ({firstAir.Year})"; string fullShowName = $"{showInfo.name} ({firstAir.Year})";
//#if !DEBUG //#if !DEBUG
var settings = await PrService.GetSettingsAsync();
// check if the show has already been requested // check if the show has already been requested
Log.Info("Requesting tv show with id {0}", showId); Log.Info("Requesting tv show with id {0}", showId);
@ -659,27 +638,10 @@ namespace PlexRequests.UI.Modules
var result = sender.SendToSonarr(sonarrSettings, model); var result = sender.SendToSonarr(sonarrSettings, model);
if (!string.IsNullOrEmpty(result?.title)) if (!string.IsNullOrEmpty(result?.title))
{ {
model.Approved = true; return await AddRequest(model, settings, $"{fullShowName} was successfully added!");
Log.Debug("Adding tv to database requests (No approval required & Sonarr)");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
var notify1 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
await NotificationService.Publish(notify1);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
} }
return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages)); return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages));
} }
var srSettings = SickRageService.GetSettings(); var srSettings = SickRageService.GetSettings();
@ -688,60 +650,25 @@ namespace PlexRequests.UI.Modules
var result = sender.SendToSickRage(srSettings, model); var result = sender.SendToSickRage(srSettings, model);
if (result?.result == "success") if (result?.result == "success")
{ {
model.Approved = true; return await AddRequest(model, settings, $"{fullShowName} was successfully added!");
Log.Debug("Adding tv to database requests (No approval required & SickRage)");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
await NotificationService.Publish(notify2);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
} }
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(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." });
} }
if (!srSettings.Enabled && !sonarrSettings.Enabled) if (!srSettings.Enabled && !sonarrSettings.Enabled)
{ {
model.Approved = true; return await AddRequest(model, settings, $"{fullShowName} was successfully added!");
Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
await NotificationService.Publish(notify2);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
} }
return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." });
} }
return await AddRequest(model, settings, $"{fullShowName} was successfully added!");
await RequestService.AddRequestAsync(model);
var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest };
await NotificationService.Publish(notificationModel);
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
} }
private bool ShouldSendNotification() private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings)
{ {
var sendNotification = true; var sendNotification = ShouldAutoApprove(type, prSettings) ? !prSettings.IgnoreNotifyForAutoApprovedRequests : true;
var claims = Context.CurrentUser?.Claims; var claims = Context.CurrentUser?.Claims;
if (claims != null) if (claims != null)
{ {
@ -758,6 +685,11 @@ namespace PlexRequests.UI.Modules
private async Task<Response> RequestAlbum(string releaseId) private async Task<Response> RequestAlbum(string releaseId)
{ {
var settings = await PrService.GetSettingsAsync(); var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.Album))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for Albums! Please contact your admin." });
}
Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, CookieHelper.GetAnalyticClientId(Cookies));
var existingRequest = await RequestService.CheckRequestAsync(releaseId); var existingRequest = await RequestService.CheckRequestAsync(releaseId);
Log.Debug("Checking for an existing request"); Log.Debug("Checking for an existing request");
@ -835,46 +767,10 @@ namespace PlexRequests.UI.Modules
var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService);
await sender.AddAlbum(model); await sender.AddAlbum(model);
model.Approved = true; return await AddRequest(model, settings, $"{model.Title} was successfully added!");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
await NotificationService.Publish(notify2);
}
return
Response.AsJson(new JsonResponseModel
{
Result = true,
Message = $"{model.Title} was successfully added!"
});
} }
if (ShouldSendNotification()) return await AddRequest(model, settings, $"{model.Title} was successfully added!");
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
await NotificationService.Publish(notify2);
}
await RequestService.AddRequestAsync(model);
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = $"{model.Title} was successfully added!"
});
} }
private string GetMusicBrainzCoverArt(string id) private string GetMusicBrainzCoverArt(string id)
@ -912,6 +808,7 @@ namespace PlexRequests.UI.Modules
private async Task<Response> NotifyUser(bool notify) private async Task<Response> NotifyUser(bool notify)
{ {
Analytics.TrackEventAsync(Category.Search, Action.Save, "NotifyUser", Username, CookieHelper.GetAnalyticClientId(Cookies), notify ? 1 : 0);
var authSettings = await Auth.GetSettingsAsync(); var authSettings = await Auth.GetSettingsAsync();
var auth = authSettings.UserAuthentication; var auth = authSettings.UserAuthentication;
var emailSettings = await EmailNotificationSettings.GetSettingsAsync(); var emailSettings = await EmailNotificationSettings.GetSettingsAsync();
@ -978,6 +875,89 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(model); return Response.AsJson(model);
} }
private async Task<bool> CheckRequestLimit(PlexRequestSettings s, RequestType type)
{
if (IsAdmin)
return true;
if (s.ApprovalWhiteList.Contains(Username))
return true;
var requestLimit = GetRequestLimitForType(type, s);
if (requestLimit == 0)
{
return true;
}
var limit = await RequestLimitRepo.GetAllAsync();
var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == type);
if (usersLimit == null)
{
// Have not set a requestLimit yet
return true;
}
return usersLimit.RequestCount >= requestLimit;
}
private int GetRequestLimitForType(RequestType type, PlexRequestSettings s)
{
int requestLimit;
switch (type)
{
case RequestType.Movie:
requestLimit = s.MovieWeeklyRequestLimit;
break;
case RequestType.TvShow:
requestLimit = s.TvWeeklyRequestLimit;
break;
case RequestType.Album:
requestLimit = s.AlbumWeeklyRequestLimit;
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
return requestLimit;
}
private async Task<Response> AddRequest(RequestedModel model, PlexRequestSettings settings, string message)
{
model.Approved = true;
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification(RequestType.Movie, settings))
{
var notificationModel = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.Movie
};
await NotificationService.Publish(notificationModel);
}
var limit = await RequestLimitRepo.GetAllAsync();
var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type);
if (usersLimit == null)
{
await RequestLimitRepo.InsertAsync(new RequestLimit
{
Username = Username,
RequestType = model.Type,
FirstRequestDate = DateTime.UtcNow,
RequestCount = 1
});
}
else
{
usersLimit.RequestCount++;
await RequestLimitRepo.UpdateAsync(usersLimit);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = message });
}
} }
} }

View file

@ -39,16 +39,21 @@ using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex; using PlexRequests.Api.Models.Plex;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
using Action = PlexRequests.Helpers.Analytics.Action;
namespace PlexRequests.UI.Modules namespace PlexRequests.UI.Modules
{ {
public class UserLoginModule : BaseModule public class UserLoginModule : BaseModule
{ {
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api, ISettingsService<PlexRequestSettings> pr, ISettingsService<LandingPageSettings> lp) : base("userlogin", pr) public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api, ISettingsService<PlexRequestSettings> pr, ISettingsService<LandingPageSettings> lp, IAnalytics a) : base("userlogin", pr)
{ {
AuthService = auth; AuthService = auth;
LandingPageSettings = lp; LandingPageSettings = lp;
Analytics = a;
Api = api; Api = api;
Get["/", true] = async (x, ct) => await Index(); Get["/", true] = async (x, ct) => await Index();
Post["/"] = x => LoginUser(); Post["/"] = x => LoginUser();
@ -58,6 +63,7 @@ namespace PlexRequests.UI.Modules
private ISettingsService<AuthenticationSettings> AuthService { get; } private ISettingsService<AuthenticationSettings> AuthService { get; }
private ISettingsService<LandingPageSettings> LandingPageSettings { get; } private ISettingsService<LandingPageSettings> LandingPageSettings { get; }
private IPlexApi Api { get; } private IPlexApi Api { get; }
private IAnalytics Analytics { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
@ -71,8 +77,18 @@ namespace PlexRequests.UI.Modules
if (landingSettings.Enabled) if (landingSettings.Enabled)
{ {
if (landingSettings.BeforeLogin) if (landingSettings.BeforeLogin)
{ {
#pragma warning disable 4014
Analytics.TrackEventAsync(
#pragma warning restore 4014
Category.LandingPage,
Action.View,
"Going To LandingPage before login",
Username,
CookieHelper.GetAnalyticClientId(Cookies));
var model = new LandingPageViewModel var model = new LandingPageViewModel
{ {
Enabled = landingSettings.Enabled, Enabled = landingSettings.Enabled,
@ -168,7 +184,7 @@ namespace PlexRequests.UI.Modules
if (!authenticated) if (!authenticated)
{ {
return Response.AsJson(new JsonResponseModel {Result = false, Message = "Incorrect User or Password"}); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" });
} }
var landingSettings = LandingPageSettings.GetSettings(); var landingSettings = LandingPageSettings.GetSettings();
@ -178,7 +194,7 @@ namespace PlexRequests.UI.Modules
if (!landingSettings.BeforeLogin) if (!landingSettings.BeforeLogin)
return Response.AsJson(new JsonResponseModel { Result = true, Message = "landing" }); return Response.AsJson(new JsonResponseModel { Result = true, Message = "landing" });
} }
return Response.AsJson(new JsonResponseModel {Result = true, Message = "search" }); return Response.AsJson(new JsonResponseModel { Result = true, Message = "search" });
} }

View file

@ -159,7 +159,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Bootstrapper.cs" /> <Compile Include="Bootstrapper.cs" />
<Compile Include="Helpers\BaseUrlHelper.cs" /> <Compile Include="Helpers\BaseUrlHelper.cs" />
<Compile Include="Helpers\ContainerHelper.cs" /> <Compile Include="Helpers\CultureHelper.cs" />
<Compile Include="Helpers\HeadphonesSender.cs" /> <Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\EmptyViewBase.cs" /> <Compile Include="Helpers\EmptyViewBase.cs" />
<Compile Include="Helpers\ServiceLocator.cs" /> <Compile Include="Helpers\ServiceLocator.cs" />
@ -197,9 +197,16 @@
<Compile Include="Modules\ApiUserModule.cs" /> <Compile Include="Modules\ApiUserModule.cs" />
<Compile Include="Modules\BaseApiModule.cs" /> <Compile Include="Modules\BaseApiModule.cs" />
<Compile Include="Modules\BaseModule.cs" /> <Compile Include="Modules\BaseModule.cs" />
<Compile Include="Modules\CultureModule.cs" />
<Compile Include="Modules\IssuesModule.cs" /> <Compile Include="Modules\IssuesModule.cs" />
<Compile Include="Modules\LandingPageModule.cs" /> <Compile Include="Modules\LandingPageModule.cs" />
<Compile Include="Modules\PlexUsersModule.cs" />
<Compile Include="Modules\UpdateCheckerModule.cs" /> <Compile Include="Modules\UpdateCheckerModule.cs" />
<Compile Include="Resources\UI.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>UI.resx</DependentUpon>
</Compile>
<Compile Include="Start\StartupOptions.cs" /> <Compile Include="Start\StartupOptions.cs" />
<Compile Include="Start\UpdateValue.cs" /> <Compile Include="Start\UpdateValue.cs" />
<Compile Include="Validators\SlackSettingsValidator.cs" /> <Compile Include="Validators\SlackSettingsValidator.cs" />
@ -570,7 +577,20 @@
<DependentUpon>web.config</DependentUpon> <DependentUpon>web.config</DependentUpon>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup>
<EmbeddedResource Include="Resources\UI.da.resx" />
<EmbeddedResource Include="Resources\UI.de.resx" />
<EmbeddedResource Include="Resources\UI.es.resx" />
<EmbeddedResource Include="Resources\UI.fr.resx" />
<EmbeddedResource Include="Resources\UI.it.resx" />
<EmbeddedResource Include="Resources\UI.nl.resx" />
<EmbeddedResource Include="Resources\UI.pt.resx" />
<EmbeddedResource Include="Resources\UI.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>UI.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\UI.sv.resx" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj"> <ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project> <Project>{95834072-A675-415D-AA8F-877C91623810}</Project>

919
PlexRequests.UI/Resources/UI.Designer.cs generated Normal file
View file

@ -0,0 +1,919 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PlexRequests.UI.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class UI {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal UI() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PlexRequests.UI.Resources.UI", typeof(UI).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Approve.
/// </summary>
public static string Common_Approve {
get {
return ResourceManager.GetString("Common_Approve", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approved.
/// </summary>
public static string Common_Approved {
get {
return ResourceManager.GetString("Common_Approved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Close.
/// </summary>
public static string Common_Close {
get {
return ResourceManager.GetString("Common_Close", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove.
/// </summary>
public static string Common_Remove {
get {
return ResourceManager.GetString("Common_Remove", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save.
/// </summary>
public static string Common_Save {
get {
return ResourceManager.GetString("Common_Save", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Issue.
/// </summary>
public static string Issues_Issue {
get {
return ResourceManager.GetString("Issues_Issue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save Changes.
/// </summary>
public static string Issues_Modal_Save {
get {
return ResourceManager.GetString("Issues_Modal_Save", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add an issue.
/// </summary>
public static string Issues_Modal_Title {
get {
return ResourceManager.GetString("Issues_Modal_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No Subtitles.
/// </summary>
public static string Issues_NoSubs {
get {
return ResourceManager.GetString("Issues_NoSubs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Other.
/// </summary>
public static string Issues_Other {
get {
return ResourceManager.GetString("Issues_Other", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Playback Issues.
/// </summary>
public static string Issues_Playback {
get {
return ResourceManager.GetString("Issues_Playback", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Wrong Audio.
/// </summary>
public static string Issues_WrongAudio {
get {
return ResourceManager.GetString("Issues_WrongAudio", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Wrong Content.
/// </summary>
public static string Issues_WrongContent {
get {
return ResourceManager.GetString("Issues_WrongContent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Something went wrong!.
/// </summary>
public static string Javascript_SomethingWentWrong {
get {
return ResourceManager.GetString("Javascript_SomethingWentWrong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Success!.
/// </summary>
public static string Javascript_Success {
get {
return ResourceManager.GetString("Javascript_Success", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Admin.
/// </summary>
public static string Layout_Admin {
get {
return ResourceManager.GetString("Layout_Admin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Change Password.
/// </summary>
public static string Layout_ChangePassword {
get {
return ResourceManager.GetString("Layout_ChangePassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Danish.
/// </summary>
public static string Layout_Danish {
get {
return ResourceManager.GetString("Layout_Danish", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Donate.
/// </summary>
public static string Layout_Donate {
get {
return ResourceManager.GetString("Layout_Donate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Dutch.
/// </summary>
public static string Layout_Dutch {
get {
return ResourceManager.GetString("Layout_Dutch", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to English.
/// </summary>
public static string Layout_English {
get {
return ResourceManager.GetString("Layout_English", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to German.
/// </summary>
public static string Layout_German {
get {
return ResourceManager.GetString("Layout_German", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Issues.
/// </summary>
public static string Layout_Issues {
get {
return ResourceManager.GetString("Layout_Issues", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Italian.
/// </summary>
public static string Layout_Italian {
get {
return ResourceManager.GetString("Layout_Italian", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Logout.
/// </summary>
public static string Layout_Logout {
get {
return ResourceManager.GetString("Layout_Logout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Portuguese.
/// </summary>
public static string Layout_Portuguese {
get {
return ResourceManager.GetString("Layout_Portuguese", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requests.
/// </summary>
public static string Layout_Requests {
get {
return ResourceManager.GetString("Layout_Requests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search.
/// </summary>
public static string Layout_Search {
get {
return ResourceManager.GetString("Layout_Search", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Settings.
/// </summary>
public static string Layout_Settings {
get {
return ResourceManager.GetString("Layout_Settings", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Spanish.
/// </summary>
public static string Layout_Spanish {
get {
return ResourceManager.GetString("Layout_Spanish", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Swedish.
/// </summary>
public static string Layout_Swedish {
get {
return ResourceManager.GetString("Layout_Swedish", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Plex Requests.
/// </summary>
public static string Layout_Title {
get {
return ResourceManager.GetString("Layout_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There is a new update available! Click.
/// </summary>
public static string Layout_UpdateAvailablePart1 {
get {
return ResourceManager.GetString("Layout_UpdateAvailablePart1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Here!.
/// </summary>
public static string Layout_UpdateAvailablePart2 {
get {
return ResourceManager.GetString("Layout_UpdateAvailablePart2", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Welcome.
/// </summary>
public static string Layout_Welcome {
get {
return ResourceManager.GetString("Layout_Welcome", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Albums.
/// </summary>
public static string Requests_AlbumsTabTitle {
get {
return ResourceManager.GetString("Requests_AlbumsTabTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approve Movies.
/// </summary>
public static string Requests_ApproveMovies {
get {
return ResourceManager.GetString("Requests_ApproveMovies", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approve Music.
/// </summary>
public static string Requests_ApproveMusic {
get {
return ResourceManager.GetString("Requests_ApproveMusic", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approve TV Shows.
/// </summary>
public static string Requests_ApproveTvShows {
get {
return ResourceManager.GetString("Requests_ApproveTvShows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Available.
/// </summary>
public static string Requests_Available {
get {
return ResourceManager.GetString("Requests_Available", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete Movies.
/// </summary>
public static string Requests_DeleteMovies {
get {
return ResourceManager.GetString("Requests_DeleteMovies", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete Music.
/// </summary>
public static string Requests_DeleteMusic {
get {
return ResourceManager.GetString("Requests_DeleteMusic", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete TV Shows.
/// </summary>
public static string Requests_DeleteTVShows {
get {
return ResourceManager.GetString("Requests_DeleteTVShows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Filter.
/// </summary>
public static string Requests_Filter {
get {
return ResourceManager.GetString("Requests_Filter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to All.
/// </summary>
public static string Requests_Filter_All {
get {
return ResourceManager.GetString("Requests_Filter_All", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approved.
/// </summary>
public static string Requests_Filter_Approved {
get {
return ResourceManager.GetString("Requests_Filter_Approved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Available.
/// </summary>
public static string Requests_Filter_Available {
get {
return ResourceManager.GetString("Requests_Filter_Available", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not Approved.
/// </summary>
public static string Requests_Filter_NotApproved {
get {
return ResourceManager.GetString("Requests_Filter_NotApproved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not Available.
/// </summary>
public static string Requests_Filter_NotAvailable {
get {
return ResourceManager.GetString("Requests_Filter_NotAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not Released.
/// </summary>
public static string Requests_Filter_NotReleased {
get {
return ResourceManager.GetString("Requests_Filter_NotReleased", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Released.
/// </summary>
public static string Requests_Filter_Released {
get {
return ResourceManager.GetString("Requests_Filter_Released", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Mark Available.
/// </summary>
public static string Requests_MarkAvailable {
get {
return ResourceManager.GetString("Requests_MarkAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Mark Unavailable.
/// </summary>
public static string Requests_MarkUnavailable {
get {
return ResourceManager.GetString("Requests_MarkUnavailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Movies.
/// </summary>
public static string Requests_MoviesTabTitle {
get {
return ResourceManager.GetString("Requests_MoviesTabTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Order.
/// </summary>
public static string Requests_Order {
get {
return ResourceManager.GetString("Requests_Order", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Latest Releases.
/// </summary>
public static string Requests_Order_LatestReleases {
get {
return ResourceManager.GetString("Requests_Order_LatestReleases", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Latest Requests.
/// </summary>
public static string Requests_Order_LatestRequests {
get {
return ResourceManager.GetString("Requests_Order_LatestRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Oldest Releases.
/// </summary>
public static string Requests_Order_OldestReleases {
get {
return ResourceManager.GetString("Requests_Order_OldestReleases", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Oldest Requests.
/// </summary>
public static string Requests_Order_OldestRequests {
get {
return ResourceManager.GetString("Requests_Order_OldestRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Below you can see yours and all other requests, as well as their download and approval status..
/// </summary>
public static string Requests_Paragraph {
get {
return ResourceManager.GetString("Requests_Paragraph", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Release Date.
/// </summary>
public static string Requests_ReleaseDate {
get {
return ResourceManager.GetString("Requests_ReleaseDate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requested By.
/// </summary>
public static string Requests_RequestedBy {
get {
return ResourceManager.GetString("Requests_RequestedBy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requested Date.
/// </summary>
public static string Requests_RequestedDate {
get {
return ResourceManager.GetString("Requests_RequestedDate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Seasons Requested.
/// </summary>
public static string Requests_SeasonsRequested {
get {
return ResourceManager.GetString("Requests_SeasonsRequested", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requests.
/// </summary>
public static string Requests_Title {
get {
return ResourceManager.GetString("Requests_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Toggle Dropdown.
/// </summary>
public static string Requests_ToggleDropdown {
get {
return ResourceManager.GetString("Requests_ToggleDropdown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to TV Shows.
/// </summary>
public static string Requests_TvShowTabTitle {
get {
return ResourceManager.GetString("Requests_TvShowTabTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Albums.
/// </summary>
public static string Search_Albums {
get {
return ResourceManager.GetString("Search_Albums", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to All Seasons.
/// </summary>
public static string Search_AllSeasons {
get {
return ResourceManager.GetString("Search_AllSeasons", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Available.
/// </summary>
public static string Search_Available {
get {
return ResourceManager.GetString("Search_Available", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Coming Soon.
/// </summary>
public static string Search_ComingSoon {
get {
return ResourceManager.GetString("Search_ComingSoon", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Country.
/// </summary>
public static string Search_Country {
get {
return ResourceManager.GetString("Search_Country", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to First Season.
/// </summary>
public static string Search_FirstSeason {
get {
return ResourceManager.GetString("Search_FirstSeason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to In Theaters.
/// </summary>
public static string Search_InTheaters {
get {
return ResourceManager.GetString("Search_InTheaters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Latest Season.
/// </summary>
public static string Search_LatestSeason {
get {
return ResourceManager.GetString("Search_LatestSeason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Seasons.
/// </summary>
public static string Search_Modal_SeasonsTitle {
get {
return ResourceManager.GetString("Search_Modal_SeasonsTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Movies.
/// </summary>
public static string Search_Movies {
get {
return ResourceManager.GetString("Search_Movies", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!.
/// </summary>
public static string Search_Paragraph {
get {
return ResourceManager.GetString("Search_Paragraph", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Report Issue.
/// </summary>
public static string Search_ReportIssue {
get {
return ResourceManager.GetString("Search_ReportIssue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Request.
/// </summary>
public static string Search_Request {
get {
return ResourceManager.GetString("Search_Request", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requested.
/// </summary>
public static string Search_Requested {
get {
return ResourceManager.GetString("Search_Requested", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Season.
/// </summary>
public static string Search_Season {
get {
return ResourceManager.GetString("Search_Season", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select .
/// </summary>
public static string Search_SelectSeason {
get {
return ResourceManager.GetString("Search_SelectSeason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Send me a notification when items I have requested have been added.
/// </summary>
public static string Search_SendNotificationText {
get {
return ResourceManager.GetString("Search_SendNotificationText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Suggestions.
/// </summary>
public static string Search_Suggestions {
get {
return ResourceManager.GetString("Search_Suggestions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search.
/// </summary>
public static string Search_Title {
get {
return ResourceManager.GetString("Search_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Track Count.
/// </summary>
public static string Search_TrackCount {
get {
return ResourceManager.GetString("Search_TrackCount", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to TV Shows.
/// </summary>
public static string Search_TvShows {
get {
return ResourceManager.GetString("Search_TvShows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Want to watch a movie or tv show but it&apos;s not currently on Plex?
/// Login below with your Plex.tv username and password!.
/// </summary>
public static string UserLogin_Paragraph {
get {
return ResourceManager.GetString("UserLogin_Paragraph", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your login details are only used to authenticate your Plex account..
/// </summary>
public static string UserLogin_Paragraph_SpanHover {
get {
return ResourceManager.GetString("UserLogin_Paragraph_SpanHover", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password.
/// </summary>
public static string UserLogin_Password {
get {
return ResourceManager.GetString("UserLogin_Password", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sign In.
/// </summary>
public static string UserLogin_SignIn {
get {
return ResourceManager.GetString("UserLogin_SignIn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login.
/// </summary>
public static string UserLogin_Title {
get {
return ResourceManager.GetString("UserLogin_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Plex.tv Username .
/// </summary>
public static string UserLogin_Username {
get {
return ResourceManager.GetString("UserLogin_Username", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Username.
/// </summary>
public static string UserLogin_Username_Placeholder {
get {
return ResourceManager.GetString("UserLogin_Username_Placeholder", resourceCulture);
}
}
}
}

View file

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Log ind</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Ønsker du at se en film eller tv-show, men det er i øjeblikket ikke på Plex? Log nedenfor med dit Plex.tv brugernavn og password !!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Dine login-oplysninger bruges kun til at godkende din Plex konto.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Brugernavn</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Brugernavn</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Adgangskode</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>log på</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Noget gik galt</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Fuldført</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Søg</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Anmodninger</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Issues</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>STØT</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Administrator</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Indstillinger</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Skift adgangskode</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Log ud</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Der er en ny opdatering tilgængelig! Klik</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Dansk</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spansk</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Tysk</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Dansk</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugisisk</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Swedish</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italiensk</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>her</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Hollandsk</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Film</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>TV-shows</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Album</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Ønsker at se noget, der ikke i øjeblikket på Plex ?! Intet problem! Bare søge efter det nedenfor og anmode den !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Søg</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Forslag</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Kommer snart</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Teatre</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Send mig en meddelelse, når emner, jeg har anmodet er blevet tilføjet</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Gem</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Tilgængelig</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Forespørgsel sendt</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Forespørgsel</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Alle sæsoner</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Første sæson</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Sidste sæson</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Vælg</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Rapport Issue</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Forkert lyd</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Undertekster</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Forkert indhold</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Playback Issues</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Andet</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Track Count</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Land</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Årstid</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Luk</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Tilføj et problem!</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Gem ændringer</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Sæson</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Velkommen</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Anmodninger</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Nedenfor kan du se dine og alle andre anmodninger, samt deres download og godkendelse status.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Film</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>TV-shows</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Album</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Slet Film</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Godkend film</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Slet tv-udsendelser</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Godkend tv-udsendelser</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Slet Music</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Godkend Music</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Godkendt</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Godkendt</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Tilgængelig</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Ikke tilgængelig</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Udgivet</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Udgivet:</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Ordrer</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Seneste anmodninger</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Ældste anmodninger</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Nyeste udgivelser</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Ældste udgivelser</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Frigivelsesdato</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Seasons Anmodet</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Anmodet Af</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Ønskede dato</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Godkend</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Fjern</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>ikke tilgængelig</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Mark tilgængelig</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Godkendt</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Tilgængelig</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Aktieemission</value>
</data>
</root>

View file

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Möchten Sie einen Film oder eine TV-Show zu sehen, aber es ist derzeit nicht auf Plex? Loggen Sie sich unten mit Ihrem Plex.tv Benutzernamen und Passwort !</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Ihre Login-Daten werden verwendet, nur Ihr Plex Konto zu authentifizieren.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Benutzername</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Benutzername</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Passwort</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Irgendetwas ist falsch gelaufen</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Erfolg</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Suche</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Anfragen</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Probleme</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Spenden</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Verwaltung</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Einstellungen</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Passwort ändern</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Ausloggen</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Es gibt ein neues Update verfügbar! Klicken</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Englisch</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanisch</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Deutsch</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Dänisch</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugiesisch</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Schwedisch</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italienisch</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>hier</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Niederländisch</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Filme</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>SERIEN</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Alben</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Möchten Sie etwas zu sehen, die derzeit nicht auf Plex ist ?! Kein Problem! Suchen Sie einfach nach unten und es es wünschen !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Suche</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Vorschläge</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Demnächst</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Theatern</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Senden Sie mir eine Benachrichtigung, wenn Gegenstände, die ich angefordert wurden hinzugefügt</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Speichern</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>V ERFÜGBAR</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>angefragt</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Angefordert</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>alle Saisonen</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Erste Saison</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Neueste Saison</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Auswählen</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Report Ausgabe</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Falsche Audio</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Keine Untertitel</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Falscher Inhaltstyp.</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Wiedergabe-Probleme</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Sonstige</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Track-Count</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Land</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Jahreszeiten</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Schließen</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Fügen Sie ein Problem</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Änderungen speichern</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Staffel</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Herzlich willkommen</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Anfragen</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Im Folgenden finden Sie Ihre und alle anderen Anfragen, sowie deren Download und Genehmigungsstatus zu sehen.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Filme</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>SERIEN</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Alben</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Löschen von Filmen</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Genehmigen-Filme</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Löschen TV Shows</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Genehmigen TV Shows</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Löschen Music</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Genehmigen Music</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Genehmigt</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Nicht bestätigt</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>V ERFÜGBAR</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Nicht verfügbar</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Frei</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Frei</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Bestellung</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter!</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Aktuelle Anfragen</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Älteste Anfragen</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Neueste Veröffentlichungen</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Die ältesten Releases</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Veröffentlichung</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Jahreszeiten heraus</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Beantragt von</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>angefragt</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Genehmigen</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Entfernen</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Nicht verfügbar</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>V ERFÜGBAR</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Genehmigt</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>V ERFÜGBAR</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Problemstellung</value>
</data>
</root>

View file

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>INICIAR SESIÓN</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>¿Quieres ver una película o programa de televisión, pero no es actualmente en Plex? Ingresa abajo con su nombre de usuario y contraseña Plex.tv !</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Sus datos de acceso sólo se utilizan para autenticar su cuenta Plex.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv nombre de usuario</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Username</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Contraseña</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Iniciar sesión</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Algo salió mal</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>¡Éxito!</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Buscar</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Peticiones</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Problemas</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Dona</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Administración</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Ajustes</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Cambiar contraseña</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Desconectarse</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Hay una nueva actualización disponible! Hacer clic</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Inglés</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanish</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>German</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danés</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugués</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Sueco</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italiano</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>aquí</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Holandés</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Películas</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Serie de TV</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Álbumes</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>¿Quieres ver algo que no se encuentra actualmente en Plex ?! ¡No hay problema! Sólo la búsqueda de abajo y que solicitarlo !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Buscar</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Sugerencias</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Muy Pronto</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>En los cines</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Envíame una notificación cuando se han añadido elementos que he solicitado</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Ahorra</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>disponible</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Pedido</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Solicitud</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Todas las temporadas</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Primera Temporada</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Última estación</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Seleccionar</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Informe del problema</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Audio mal</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Subtitles</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Contenido incorrecto</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problemas de reproducción</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Otro</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>El número de pistas</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>País</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Temporadas</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Cerrar</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Añadir un problema</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Guardar cambios</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Temporada</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Bienvenido</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Peticiones</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>A continuación se puede ver la suya y todas las demás solicitudes, así como su descarga y aprobación de estado.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Películas</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Serie de TV</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Álbumes</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Eliminar películas</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Aprobar películas</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Eliminar programas de televisión</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Aprobar programas de televisión</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Eliminar la música</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Aprobar la música</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Todo</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Aprobado</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>No Aprovado</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>disponible</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>No disponible</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Publicado</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Publicado</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Pedido</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filtra</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Las últimas peticiones</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Las solicitudes más antiguas</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Últimos Lanzamientos</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Estrenos más antiguas</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Fecha de lanzamiento</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Estaciones solicitado</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>SOLICITADO POR</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>¡Fecha solicitada</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Desplegable</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Trabajo aprobado</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Eliminar</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Marcar como no disponible</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>disponible</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Aprobado</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>disponible</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>emitir</value>
</data>
</root>

View file

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Se connecter</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Vous voulez regarder un film ou la télévision, mais il est pas actuellement sur Plex? Connectez-vous ci-dessous avec votre nom d'utilisateur et mot de passe Plex.tv!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Vos informations de connexion sont uniquement utilisées pour authentifier votre compte Plex.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Nom d'utilisateur</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nom dutilisateur</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Mot de passe</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Se connecter</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Quelque-chose s'est mal passé</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Succès</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Chercher</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Requêtes</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Sortie</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Faire Un Don</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>d'Administration</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Paramètres</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Modifier le mot de passe</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Déconnexion</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Il y a une nouvelle mise à jour disponible! Cliquez</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Anglais</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Espagnol</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Allemand</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danois</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugais</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Suédois (homonymie)</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italien</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>ici</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Néerlandais</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Films</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Émissions de télévision</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Vous voulez regarder quelque chose qui est pas actuellement sur Plex ?! Pas de problème! Il suffit de chercher ci-dessous et demander !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Chercher</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggestions</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Bientôt disponible </value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Théâtre</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Envoyez-moi une notification lorsque des éléments que j'ai demandés ont été ajoutés</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Enregistrer</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Disponible</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Demandé</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Requête</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Saisons</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Première saison</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Dernière saison</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Sélectionner</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Formulaire de rapport d'incident</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Mauvais Audio</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Pas de sous-titres</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Contenu erroné</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problèmes de lecture</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Autre</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Nombre de pistes</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Pays</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Saisons</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Fermer</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Ajouter une question</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Sauvegarder les modifications</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>SAISON</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Bienvenue</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Requêtes</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Ci-dessous vous pouvez voir la vôtre et toutes les autres demandes, ainsi que le téléchargement et l'état d'approbation</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Films</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Émissions de télévision</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Supprimer Films</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Approuver Films</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Supprimer Séries TV</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Approuver Séries TV</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Supprimer la musique</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Approuver la musique</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Tous</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Approuvé</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Par voie orale pas approuvée</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Disponible</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Non disponible</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Publié</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Publié</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Commandez </value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filtrer</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Dernières demandes</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Le plus ancien demandes</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Dernières informations</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Le plus ancien de presse</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Date de commercialisation</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Seasons Demandés</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Demandé par</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Date demandée</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Basculer Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Approuver</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Supprimer</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Marquer comme indisponible</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Marquer comme disponible</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Approuvé</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Disponible</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Question en litige</value>
</data>
</root>

View file

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Accesso</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Vuoi guardare un film o tv ma non è attualmente in Plex? Effettua il login con il tuo username e la password Plex.tv !</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>I dati di accesso vengono utilizzati solo per autenticare l&amp;#39;account Plex.!</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Nome utente</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nome utente</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Parola d'ordine</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Accedi</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Errore</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Successo</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>C'è un nuovo aggiornamento disponibile! Clic</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Cerca!</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Requests</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>problemi quantificati</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Donazione</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>admin</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Impostazioni</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Modifica password</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Disconnettersi</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Inglese</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spagnolo</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Tedesco</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danese</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portoghese</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Svedese</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italiano</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>Qui!</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Olandese</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Cerca</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Film</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Spettacoli TV</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Album</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Voglia di guardare qualcosa che non è attualmente il Plex?! Non c'è problema! Basta cercare per esso qui sotto e richiederla!</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggerimenti</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Novita</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Nei Cinema</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Inviami una notifica quando sono stati aggiunti elementi che ho chiesto!</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Salva</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>disponibili</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Richiesto</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Richiedi</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Tutte le Stagioni</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Prima stagione</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Ultima stagione</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>seleziona</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Segnala il problema</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Audio sbagliato</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Nessun sottotitolo</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Contenuto sbagliato</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problemi di riproduzione</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Altre</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Conta di pista</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Nazione</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Stagioni</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Chiudi</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Aggiungere un problema</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Salva Cambia</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Stagione</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Benvenuta</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Requests</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Qui sotto potete vedere il vostro e tutte le altre richieste, così come il loro download e l'approvazione dello stato</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Film</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Spettacoli TV</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Album</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Cancellare i filmati</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Approva Film</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Cancellare programmi TV</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Approvare programmi TV</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Eliminare la musica</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Approva Musica</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Tutto ciò</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Approvato</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Approvato</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>disponibili</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Non disponibile</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Rilasciato</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Non rilasciato</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Ordina</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filtro</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Ultimi Richieste</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>I più vecchi richieste</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Ultime uscite</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>I più vecchi uscite</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Data di disponibilità</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Stagioni obbligatorio</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Richiesto</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Richiesto</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Imposta/rimuovi a discesa</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Approva</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Togliere</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Segna non disponibile</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Segna disponibile</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Approvato</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>disponibili</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Problema</value>
</data>
</root>

View file

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Inloggen</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Wilt u een film of tv-show te kijken, maar het is op het moment niet op Plex? Log hieronder in met uw gebruikersnaam en wachtwoord Plex.tv !!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Uw login gegevens worden alleen gebruikt om uw account te verifiëren Plex.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Gebruikersnaam</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Gebruikersnaam</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Wachtwoord</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Aanmelden!</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Er is iets fout gegaan</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Succes</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Zoeken</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Verzoeken</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>UItgaves</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Doneren</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Instellingen</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Wachtwoord wijzigen</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Uitloggen</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Er is een nieuwe update beschikbaar is! Klik</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>English</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanish</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>German</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Koffiebroodje</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portuguese</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Zweeds</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italian</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>Hier!</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Dutch</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Zoeken</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Aangevraagd</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>films</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>TV programma's</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>albums</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Wilt u kijken naar iets dat is momenteel niet op de Plex?! Geen probleem! Onderzoek enkel naar het hieronder en vraag het!</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggesties</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Binnenkort verwacht</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>In de Theaters</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Stuur me een bericht wanneer objecten die ik heb gevraagd zijn toegevoegd</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Opslaan</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Beschikbaar</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Verzoek</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Alle seizoenen</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Eerste seizoen</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Laatste seizoen</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Selecteer</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Probleem melden</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Verkeerde Audio</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Geen ondertiteling</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Verkeerde inhoud</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problemen met het afspelen</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Overig</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Spoor de graaf</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Land</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Seasons</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Dichtbij</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Een actie-item toevoegen</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Wijzigingen opslaan</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Seizoen</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Welkom</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Verzoeken</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Hieronder vindt u de jouwe en alle andere verzoeken kan zien, evenals hun download en goedkeuring-status.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>films</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>TV programma's</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>albums</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Verwijder Movies</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Goedkeuren Movies</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Verwijder TV Shows</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Goedkeuren TV Shows</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Verwijderen Muziek</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Goedkeuren Music</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Goedgekeurd</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Nog niet gestart</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Beschikbaar</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Niet beschikbaar</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Released</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Niet vrijgegeven</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Bestel</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter!</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Laatste aanvragen</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Oudste aanvragen</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Nieuwste releases</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Oudste Releases</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Uitgiftedatum</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Seasons gevraagd</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Verzocht door</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Aanvraag datum</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Goedkeuren</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>verwijder</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Niet beschikbaar</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Mark beschikbaar</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Goedgekeurd</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Beschikbaar</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Uitgave</value>
</data>
</root>

View file

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Entrar</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Quer assistir a um filme ou programa de TV, mas não está atualmente em Plex? Entre abaixo com seu nome de usuário e senha Plex.tv !!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Seus dados de login são apenas usados para autenticar sua conta Plex.!</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv usuário</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nome de usuário</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Senha</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Assinar em!</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Alguma coisa saiu errada.</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Sucesso</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Buscar</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Pedidos</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Issues</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Doar</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Administrativo</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Configurações</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Alterar Senha</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Sair</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Há uma nova atualização disponível! Clique</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Inglês</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Espanhol</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Alemão</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Dinamarquês</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Português</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Sueco</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italiano</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>aqui</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Holandês</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Filmes</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Todas as Series de TV</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Álbuns</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Quer assistir algo que não está atualmente em Plex ?! Sem problemas! Basta procurá-lo abaixo e solicitá-lo !!</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Buscar</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Sugestões</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Em breve!</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>somente nos cinemas</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Envie-me uma notificação quando os itens I solicitados foram adicionados!</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Salvar</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Disponível</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Requeridos</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Pedido</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Todas as temporadas</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Primeira Temporada</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Últimas estação</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Selecione</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>relatório do problema</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Áudio errado</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Sem legendas</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Conteúdo errado</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problemas de reprodução</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Outra</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Contagem pista</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>País</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Temporadas</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Fechar</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Adicionar um problema</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Salvar alterações</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Temporada</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Bem vinda</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Pedidos</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Abaixo você pode ver o seu e todos os outros pedidos, bem como o seu estado de download e aprovação.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Filmes</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Todas as Series de TV</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Álbuns</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Apagar filmes</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Aprovar filmes</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Excluir programas televisivo</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Aprovar programas televisivo</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Excluir música</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Aprovar a música</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>tudo</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Aprovado</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Aprovado</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Disponível</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Não disponível</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Liberada</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Liberada</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>de Fabricação</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filtro!</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Últimos pedidos</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Mais antigos pedidos</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Últimos Lançamentos</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Mais antigos Lançamentos</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Data de liberação</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Estações pedida</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>solicitado por</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>data solicitada</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Alternar Menu Suspenso</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Aprovar</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>REMOVER</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Marcar como indisponível</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Marcar como disponível</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Aprovado</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Disponível</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Questão</value>
</data>
</root>

View file

@ -0,0 +1,386 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Login</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Want to watch a movie or tv show but it's not currently on Plex?
Login below with your Plex.tv username and password!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Your login details are only used to authenticate your Plex account.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Username </value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Username</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Password</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Sign In</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Something went wrong!</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Success!</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Search</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Requests</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Issues</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Donate</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Change Password</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>There is a new update available! Click</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>English</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanish</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>German</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danish</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portuguese</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Swedish</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italian</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>Here!</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Dutch</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Movies</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>TV Shows</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Search</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggestions</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Coming Soon</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>In Theaters</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Send me a notification when items I have requested have been added</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Save</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Available</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Requested</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Request</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>All Seasons</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>First Season</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Latest Season</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Select </value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Report Issue</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Wrong Audio</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>No Subtitles</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Wrong Content</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Playback Issues</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Other</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Track Count</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Country</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Seasons</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Close</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Add an issue</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Save Changes</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Season</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Welcome</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Requests</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Below you can see yours and all other requests, as well as their download and approval status.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Movies</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>TV Shows</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Delete Movies</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Approve Movies</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Delete TV Shows</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Approve TV Shows</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Delete Music</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Approve Music</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>All</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Approved</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Not Approved</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Available</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Not Available</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Released</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Not Released</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Order</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Latest Requests</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Oldest Requests</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Latest Releases</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Oldest Releases</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Release Date</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Seasons Requested</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Requested By</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Requested Date</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Approve</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Remove</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Mark Unavailable</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Mark Available</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Approved</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Available</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Issue</value>
</data>
</root>

View file

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Logga in</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Vill du titta på en film eller TV-show, men det är inte närvarande på Plex? Logga in nedan med Plex.tv användarnamn och lösenord !!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Dina inloggningsuppgifter används endast för att autentisera ditt Plex-konto.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv användarnamn</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Användarnamn</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Lösenord</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Logga in</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Något gick fel</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Lyckades</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Sök</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Begäran</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Frågor</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Donera</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>admin</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Inställningar</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Byt lösenord</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Logga ut</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Det finns en ny uppdatering tillgänglig! Klick</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Svenska</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanska</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Tyska</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danska</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugisiska</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Svenska</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italienska</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>Här</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>dutch</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Fråga</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Filmer</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Tv program</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Album</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Vill titta på något som inte är närvarande på Plex ?! Inga problem! Bara söka efter den nedan och begär det !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Sök</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Förslag</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Kommer snart</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Teater</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Skicka mig ett meddelande när objekt jag har begärt har lagts till!</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Spara</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Tillgänglig</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Begärd</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Alla säsonger</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Första säsongen</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Senaste säsongen</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Välj</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Rapporten fråga</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Fel ljud</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Inga undertexter</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Fel innehåll</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Uppspelningsproblem</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Annat</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Spår räknas</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Land</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Säsonger</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Stäng</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Lägg till en fråga</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Spara Ändringar</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Årstid</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Välkommen</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Begäran</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Nedan kan du se din och alla andra förfrågningar, liksom deras nedladdning och godkännandestatus.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Filmer</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Tv program</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Album</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Radera filmer</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Godkänn filmer</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Radera TV-program</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Godkänna TV-program</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Radera musik</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Godkänn musik</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Alla</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Godkänd</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Ej Godkänd</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Tillgänglig</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Inte tillgängligt</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Släppte ut</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Inte släppt</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Beställning</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Senaste förfrågningar</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Äldsta önskemål</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Senaste versionerna</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Äldsta meddelanden</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Släpptes</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Säsonger Requested</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Begärd av</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Önskat datum</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Växla rullgardinslista</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Godkänn</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>ta bort</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Ej tillgänglig</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Tillgänglig</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Godkänd</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Tillgänglig</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Problem</value>
</data>
</root>

View file

@ -45,6 +45,7 @@ namespace PlexRequests.UI.Validators
RuleFor(x => x.BaseUrl).NotEqual("updatechecker").WithMessage("You cannot use 'updatechecker' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("updatechecker").WithMessage("You cannot use 'updatechecker' as this is reserved by the application.");
RuleFor(x => x.BaseUrl).NotEqual("usermanagement").WithMessage("You cannot use 'usermanagement' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("usermanagement").WithMessage("You cannot use 'usermanagement' as this is reserved by the application.");
RuleFor(x => x.BaseUrl).NotEqual("api").WithMessage("You cannot use 'api' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("api").WithMessage("You cannot use 'api' as this is reserved by the application.");
RuleFor(x => x.BaseUrl).NotEqual("landing").WithMessage("You cannot use 'landing' as this is reserved by the application.");
} }
} }
} }

View file

@ -5,9 +5,11 @@
@{ @{
var baseUrl = Html.GetBaseUrl(); var baseUrl = Html.GetBaseUrl();
var formAction = "/admin/loglevel"; var formAction = "/admin/loglevel";
var clearAction = "/admin/clearlogs";
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{ {
formAction = "/" + baseUrl.ToHtmlString() + formAction; formAction = "/" + baseUrl.ToHtmlString() + formAction;
clearAction = "/" + baseUrl.ToHtmlString() + clearAction;
} }
} }
@ -35,6 +37,10 @@
</div> </div>
</form> </form>
<form method="post" id="clearForm" action="@clearAction">
<button id="clearLogs" type="submit" class="btn btn-danger-outline ">Clear Logs</button>
</form>
<table id="example" class="table table-striped table-hover table-responsive"> <table id="example" class="table table-striped table-hover table-responsive">
<thead> <thead>
<tr> <tr>
@ -118,6 +124,28 @@
}); });
}); });
$('#clearLogs').click(function() {
e.preventDefault();
var $form = $("#clearForm");
$.ajax({
type: $form.prop("method"),
url: $form.prop("action"),
dataType: "json",
success: function (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> </script>

View file

@ -61,6 +61,15 @@
<input type="text" class="form-control form-control-custom " id="StoreCleanup" name="StoreCleanup" value="@Model.StoreCleanup"> <input type="text" class="form-control form-control-custom " id="StoreCleanup" name="StoreCleanup" value="@Model.StoreCleanup">
</div> </div>
</div> </div>
<small>Please note, this will not reset the users request limit, it will just check every X hours to see if it needs to be reset.</small>
<div class="form-group">
<label for="UserRequestLimitResetter" class="control-label">User Request Limit Reset (hour)</label>
<div>
<input type="text" class="form-control form-control-custom " id="UserRequestLimitResetter" name="UserRequestLimitResetter" value="@Model.UserRequestLimitResetter">
</div>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button id="save" type="submit" class="btn btn-primary-outline ">Submit</button> <button id="save" type="submit" class="btn btn-primary-outline ">Submit</button>

View file

@ -78,14 +78,14 @@
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@if (Model.SearchForMovies) @if (Model.SearchForMovies)
{ {
<input type="checkbox" id="SearchForMovies" name="SearchForMovies" checked="checked"><label for="SearchForMovies">Search for Movies</label> <input type="checkbox" id="SearchForMovies" name="SearchForMovies" checked="checked"><label for="SearchForMovies">Search for Movies</label>
} }
else else
{ {
<input type="checkbox" id="SearchForMovies" name="SearchForMovies"><label for="SearchForMovies">Search for Movies</label> <input type="checkbox" id="SearchForMovies" name="SearchForMovies"><label for="SearchForMovies">Search for Movies</label>
} }
</div> </div>
</div> </div>
@ -93,55 +93,55 @@
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@if (Model.SearchForTvShows) @if (Model.SearchForTvShows)
{ {
<input type="checkbox" id="SearchForTvShows" name="SearchForTvShows" checked="checked"><label for="SearchForTvShows">Search for TV Shows</label> <input type="checkbox" id="SearchForTvShows" name="SearchForTvShows" checked="checked"><label for="SearchForTvShows">Search for TV Shows</label>
} }
else else
{ {
<input type="checkbox" id="SearchForTvShows" name="SearchForTvShows"><label for="SearchForTvShows">Search for TV Shows</label> <input type="checkbox" id="SearchForTvShows" name="SearchForTvShows"><label for="SearchForTvShows">Search for TV Shows</label>
} }
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@if (Model.SearchForMusic) @if (Model.SearchForMusic)
{ {
<input type="checkbox" id="SearchForMusic" name="SearchForMusic" checked="checked"><label for="SearchForMusic">Search for Music</label> <input type="checkbox" id="SearchForMusic" name="SearchForMusic" checked="checked"><label for="SearchForMusic">Search for Music</label>
} }
else else
{ {
<input type="checkbox" id="SearchForMusic" name="SearchForMusic"><label for="SearchForMusic">Search for Music</label> <input type="checkbox" id="SearchForMusic" name="SearchForMusic"><label for="SearchForMusic">Search for Music</label>
} }
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@if (Model.RequireMovieApproval) @if (Model.RequireMovieApproval)
{ {
<input type="checkbox" id="RequireMovieApproval" name="RequireMovieApproval" checked="checked"><label for="RequireMovieApproval">Require approval of Movie requests</label> <input type="checkbox" id="RequireMovieApproval" name="RequireMovieApproval" checked="checked"><label for="RequireMovieApproval">Require approval of Movie requests</label>
} }
else else
{ {
<input type="checkbox" id="RequireMovieApproval" name="RequireMovieApproval"><label for="RequireMovieApproval">Require approval of Movie requests</label> <input type="checkbox" id="RequireMovieApproval" name="RequireMovieApproval"><label for="RequireMovieApproval">Require approval of Movie requests</label>
} }
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@if (Model.RequireTvShowApproval) @if (Model.RequireTvShowApproval)
{ {
<input type="checkbox" id="RequireTvShowApproval" name="RequireTvShowApproval" checked="checked"><label for="RequireTvShowApproval">Require approval of TV show requests</label> <input type="checkbox" id="RequireTvShowApproval" name="RequireTvShowApproval" checked="checked"><label for="RequireTvShowApproval">Require approval of TV show requests</label>
} }
else else
{ {
<input type="checkbox" id="RequireTvShowApproval" name="RequireTvShowApproval"><label for="RequireTvShowApproval">Require approval of TV show requests</label> <input type="checkbox" id="RequireTvShowApproval" name="RequireTvShowApproval"><label for="RequireTvShowApproval">Require approval of TV show requests</label>
} }
</div> </div>
@ -149,14 +149,14 @@
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@if (Model.RequireMusicApproval) @if (Model.RequireMusicApproval)
{ {
<input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval" checked="checked"><label for="RequireMusicApproval">Require approval of Music requests</label> <input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval" checked="checked"><label for="RequireMusicApproval">Require approval of Music requests</label>
} }
else else
{ {
<input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval"><label for="RequireMusicApproval">Require approval of Music requests</label> <input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval"><label for="RequireMusicApproval">Require approval of Music requests</label>
} }
</div> </div>
@ -194,6 +194,21 @@
</div> </div>
</div> </div>
<div class="form-group">
<div class="checkbox">
@if (Model.IgnoreNotifyForAutoApprovedRequests)
{
<input type="checkbox" id="IgnoreNotifyForAutoApprovedRequests" name="IgnoreNotifyForAutoApprovedRequests" checked="checked">
<label for="IgnoreNotifyForAutoApprovedRequests">Do not send notifications for requests that don't require approval</label>
}
else
{
<input type="checkbox" id="IgnoreNotifyForAutoApprovedRequests" name="IgnoreNotifyForAutoApprovedRequests"><label for="IgnoreNotifyForAutoApprovedRequests">Do not send notifications for requests that don't require approval</label>
}
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@ -211,24 +226,44 @@
<p class="form-group">A comma separated list of users whose requests do not require approval.</p> <p class="form-group">A comma separated list of users whose requests do not require approval (These users also do not have a request limit).</p>
<div class="form-group"> <div class="form-group">
<label for="noApprovalUsers" class="control-label">Approval White listed Users</label> <label for="NoApprovalUsers" class="control-label">Approval White listed Users</label>
<div> <div>
<input type="text" class="form-control-custom form-control " id="NoApprovalUsers" name="NoApprovalUsers" placeholder="e.g. John, Bobby" value="@Model.NoApprovalUsers"> <input type="text" class="form-control-custom form-control " id="NoApprovalUsers" name="NoApprovalUsers" placeholder="e.g. John, Bobby" value="@Model.NoApprovalUsers">
</div> </div>
</div> </div>
@*<div class="form-group">
<label for="WeeklyRequestLimit" class="control-label">Weekly Request Limit</label> <p class="form-group">If the request limits are set to 0 then no request limit is applied.</p>
<div class="form-group">
<label for="MovieWeeklyRequestLimit" class="control-label">Movie Weekly Request Limit</label>
<div>
<label>
<input type="number" id="MovieWeeklyRequestLimit" name="MovieWeeklyRequestLimit" class="form-control form-control-custom " value="@Model.MovieWeeklyRequestLimit">
</label>
</div>
</div>
<div class="form-group">
<label for="TvWeeklyRequestLimit" class="control-label">TV Show Weekly Request Limit</label>
<div>
<label>
<input type="number" id="TvWeeklyRequestLimit" name="TvWeeklyRequestLimit" class="form-control form-control-custom " value="@Model.TvWeeklyRequestLimit">
</label>
</div>
</div>
<div class="form-group">
<label for="AlbumWeeklyRequestLimit" class="control-label">Album Weekly Request Limit</label>
<div> <div>
<label> <label>
<input type="number" id="WeeklyRequestLimit" name="WeeklyRequestLimit" class="form-control form-control-custom " value="@Model.WeeklyRequestLimit"> <input type="number" id="AlbumWeeklyRequestLimit" name="AlbumWeeklyRequestLimit" class="form-control form-control-custom " value="@Model.AlbumWeeklyRequestLimit">
</label> </label>
</div> </div>
</div> //TODO: Need to implement this*@ </div>
<div> <div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
@ -240,9 +275,9 @@
</div> </div>
<script> <script>
$(function() { $(function () {
$('#save').click(function (e) { $('#save').click(function (e) {
e.preventDefault(); e.preventDefault();
var theme = $("#themes option:selected").val(); var theme = $("#themes option:selected").val();
var $form = $("#mainForm"); var $form = $("#mainForm");
@ -270,11 +305,11 @@ $(function() {
}); });
$('#refreshKey').click(function (e) { $('#refreshKey').click(function (e) {
e.preventDefault(); e.preventDefault();
var base = '@Html.GetBaseUrl()'; var base = '@Html.GetBaseUrl()';
var url = createBaseUrl(base, '/admin/createapikey'); var url = createBaseUrl(base, '/admin/createapikey');
$.ajax({ $.ajax({
type: "post", type: "post",
url: url, url: url,
dataType: "json", dataType: "json",
@ -282,7 +317,7 @@ $(function() {
if (response) { if (response) {
generateNotify("Success!", "success"); generateNotify("Success!", "success");
$('#apiKey').val(response); $('#apiKey').val(response);
} }
}, },
error: function (e) { error: function (e) {
console.log(e); console.log(e);
@ -290,5 +325,5 @@ $(function() {
} }
}); });
}); });
}); });
</script> </script>

View file

@ -42,7 +42,7 @@
} }
@if (Model.IssueStatus == IssueStatus.ResolvedIssue) @if (Model.IssueStatus == IssueStatus.ResolvedIssue)
{ {
<form action="@formAction/issues/remove" method="post"> <form action="@formAction/issues/remove" method="post" id="removeForm">
<input id="issueId" name="issueId" value="@Model.Id" hidden="hidden" /> <input id="issueId" name="issueId" value="@Model.Id" hidden="hidden" />
<button type="submit" id="@Model.Id" class="btn btn-sm btn-danger-outline dropdown-toggle delete">Remove</button> <button type="submit" id="@Model.Id" class="btn btn-sm btn-danger-outline dropdown-toggle delete">Remove</button>
</form> </form>

View file

@ -26,7 +26,7 @@
<h4>Type</h4> <h4>Type</h4>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<h4>Issue's</h4> <h4>Issues</h4>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
@ -61,7 +61,7 @@
</div> </div>
<script id="issue-template" type="text/x-handlebars-template"> <script id="issue-template" type="text/x-handlebars-template">
<div> <div id="{{id}}Template">
<div class="row"> <div class="row">
<div class="col-md-3"> <div class="col-md-3">
<div id="title">{{title}}</div> <div id="title">{{title}}</div>
@ -83,10 +83,12 @@
<div class="col-sm-1"> <div class="col-sm-1">
<a href="" id="{{id}}link" class="btn btn-sm btn-info-outline approve"><i class="fa fa-info"></i> Details</a> <a href="" id="{{id}}link" class="btn btn-sm btn-info-outline approve"><i class="fa fa-info"></i> Details</a>
<br /> <br />
<form action="@formAction/issues/remove" method="post"> {{#if admin}}
<form action="@formAction/issues/remove" method="post" id="delete{{id}}">
<input id="issueId" name="issueId" value="{{id}}" hidden="hidden" /> <input id="issueId" name="issueId" value="{{id}}" hidden="hidden" />
<button type="submit" id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle delete">Remove</button> <button type="submit" id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle delete">Remove</button>
</form> </form>
{{/if}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,8 +1,15 @@
 
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@inherits PlexRequests.UI.Helpers.EmptyViewBase<PlexRequests.UI.Models.LandingPageViewModel> @inherits PlexRequests.UI.Helpers.EmptyViewBase<PlexRequests.UI.Models.LandingPageViewModel>
@{
<img class="landing-header" src="~/Content/images/logo.png" width="300" /> var baseUrl = Html.GetBaseUrl();
var formAction = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
formAction = "/" + baseUrl.ToHtmlString();
}
}
<img class="landing-header" src="@formAction/Content/images/logo.png" width="300" />
<div id="area" class="landing-block"> <div id="area" class="landing-block">
@if (Model.NoticeEnable && (!Model.EnabledNoticeTime || Model.NoticeActive)) @if (Model.NoticeEnable && (!Model.EnabledNoticeTime || Model.NoticeActive))
{ {

View file

@ -1,5 +1,6 @@
@using Nancy.Security @using Nancy.Security
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@using PlexRequests.UI.Resources
@{ @{
var baseUrl = Html.GetBaseUrl(); var baseUrl = Html.GetBaseUrl();
var formAction = string.Empty; var formAction = string.Empty;
@ -9,23 +10,23 @@
} }
} }
<div> <div>
<h1>Requests</h1> <h1>@UI.Requests_Title</h1>
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4> <h4>@UI.Requests_Paragraph</h4>
<br /> <br />
<!-- Nav tabs --> <!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist"> <ul id="nav-tabs" class="nav nav-tabs" role="tablist">
@if (Model.SearchForMovies) @if (Model.SearchForMovies)
{ {
<li role="presentation" class="active"><a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i>Movies</a></li> <li role="presentation" class="active"><a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> @UI.Requests_MoviesTabTitle</a></li>
} }
@if (Model.SearchForTvShows) @if (Model.SearchForTvShows)
{ {
<li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i>TV Shows</a></li> <li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i >@UI.Requests_TvShowTabTitle</a></li>
} }
@if (Model.SearchForMusic) @if (Model.SearchForMusic)
{ {
<li role="presentation"><a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i>Albums</a></li> <li role="presentation"><a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i> @UI.Requests_AlbumsTabTitle</a></li>
} }
</ul> </ul>
<br /> <br />
@ -40,46 +41,46 @@
{ {
@if (Model.SearchForMovies) @if (Model.SearchForMovies)
{ {
<button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> Delete Movies</button> <button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> @UI.Requests_DeleteMovies</button>
<button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> Approve Movies</button> <button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> @UI.Requests_ApproveMovies</button>
} }
@if (Model.SearchForTvShows) @if (Model.SearchForTvShows)
{ {
<button id="deleteTVShows" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> Delete TV Shows</button> <button id="deleteTVShows" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> @UI.Requests_DeleteTVShows</button>
<button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve TV Shows</button> <button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> @UI.Requests_ApproveTvShows</button>
} }
@if (Model.SearchForMusic) @if (Model.SearchForMusic)
{ {
<button id="deleteMusic" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> Delete Music</button> <button id="deleteMusic" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> @UI.Requests_DeleteMusic</button>
<button id="approveMusic" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve Music</button> <button id="approveMusic" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> @UI.Requests_ApproveMusic</button>
} }
} }
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Filter @UI.Requests_Filter
<i class="fa fa-filter"></i> <i class="fa fa-filter"></i>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="#" class="filter" data-filter="all"><i class="fa fa-check-square"></i> All</a></li> <li><a href="#" class="filter" data-filter="all"><i class="fa fa-check-square"></i> @UI.Requests_Filter_All</a></li>
<li><a href="#" class="filter" data-filter=".approved-true"><i class="fa fa-square-o"></i> Approved</a></li> <li><a href="#" class="filter" data-filter=".approved-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Approved</a></li>
<li><a href="#" class="filter" data-filter=".approved-false"><i class="fa fa-square-o"></i> Not Approved</a></li> <li><a href="#" class="filter" data-filter=".approved-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotApproved</a></li>
<li><a href="#" class="filter" data-filter=".available-true"><i class="fa fa-square-o"></i> Available</a></li> <li><a href="#" class="filter" data-filter=".available-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Available</a></li>
<li><a href="#" class="filter" data-filter=".available-false"><i class="fa fa-square-o"></i> Not Available</a></li> <li><a href="#" class="filter" data-filter=".available-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotAvailable</a></li>
<li><a href="#" class="filter" data-filter=".released-true"><i class="fa fa-square-o"></i> Released</a></li> <li><a href="#" class="filter" data-filter=".released-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Released</a></li>
<li><a href="#" class="filter" data-filter=".released-false"><i class="fa fa-square-o"></i> Not Released</a></li> <li><a href="#" class="filter" data-filter=".released-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotReleased</a></li>
</ul> </ul>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Order @UI.Requests_Order
<i class="fa fa-sort"></i> <i class="fa fa-sort"></i>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="#" class="sort" data-sort="requestorder:desc"><i class="fa fa-check-square"></i> Latest Requests</a></li> <li><a href="#" class="sort" data-sort="requestorder:desc"><i class="fa fa-check-square"></i> @UI.Requests_Order_LatestRequests</a></li>
<li><a href="#" class="sort" data-sort="requestorder:asc"><i class="fa fa-square-o"></i> Oldest Requests</a></li> <li><a href="#" class="sort" data-sort="requestorder:asc"><i class="fa fa-square-o"></i> @UI.Requests_Order_OldestRequests</a></li>
<li><a href="#" class="sort" data-sort="releaseorder:desc"><i class="fa fa-square-o"></i> Latest Releases</a></li> <li><a href="#" class="sort" data-sort="releaseorder:desc"><i class="fa fa-square-o"></i> @UI.Requests_Order_LatestReleases</a></li>
<li><a href="#" class="sort" data-sort="releaseorder:asc"><i class="fa fa-square-o"></i> Oldest Releases</a></li> <li><a href="#" class="sort" data-sort="releaseorder:asc"><i class="fa fa-square-o"></i> @UI.Requests_Order_OldestReleases</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@ -152,9 +153,9 @@
<span class="label label-success">{{status}}</span> <span class="label label-success">{{status}}</span>
</div> </div>
<br /> <br />
<div>Release Date: {{releaseDate}}</div> <div>@UI.Requests_ReleaseDate: {{releaseDate}}</div>
<div> <div>
Approved: @UI.Common_Approved:
{{#if_eq approved false}} {{#if_eq approved false}}
<i id="{{requestId}}notapproved" class="fa fa-times"></i> <i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}} {{/if_eq}}
@ -163,7 +164,7 @@
{{/if_eq}} {{/if_eq}}
</div> </div>
<div> <div>
Available @UI.Requests_Available
{{#if_eq available false}} {{#if_eq available false}}
<i id="availableIcon{{requestId}}" class="fa fa-times"></i> <i id="availableIcon{{requestId}}" class="fa fa-times"></i>
{{/if_eq}} {{/if_eq}}
@ -172,14 +173,14 @@
{{/if_eq}} {{/if_eq}}
</div> </div>
{{#if_eq type "tv"}} {{#if_eq type "tv"}}
<div>Seasons Requested: {{seriesRequested}}</div> <div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
{{/if_eq}} {{/if_eq}}
{{#if requestedUsers}} {{#if requestedUsers}}
<div>Requested By: {{requestedUsers}}</div> <div>@UI.Requests_RequestedBy: {{requestedUsers}}</div>
{{/if}} {{/if}}
<div>Requested Date: {{requestedDate}}</div> <div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
<div> <div>
Issue: @UI.Issues_Issue:
{{#if_eq issueId 0}} {{#if_eq issueId 0}}
<i class="fa fa-times"></i> <i class="fa fa-times"></i>
{{else}} {{else}}
@ -194,10 +195,10 @@
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" /> <input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
{{#if_eq hasQualities true}} {{#if_eq hasQualities true}}
<div class="btn-group btn-split"> <div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline approve" id="{{requestId}}" custom-button="{{requestId}}"><i class="fa fa-plus"></i> Approve</button> <button type="button" class="btn btn-sm btn-success-outline approve" id="{{requestId}}" custom-button="{{requestId}}"><i class="fa fa-plus"></i> @UI.Common_Approve</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span> <span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span> <span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{#each qualities}} {{#each qualities}}
@ -206,21 +207,21 @@
</ul> </ul>
</div> </div>
{{else}} {{else}}
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button> <button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> @UI.Common_Approve</button>
{{/if_eq}} {{/if_eq}}
</form> </form>
{{/if_eq}} {{/if_eq}}
<form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}"> <form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" /> <input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> Remove</button> <button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> @UI.Common_Remove</button>
</form> </form>
<form method="POST" action="@formAction/requests/changeavailability" id="change{{requestId}}"> <form method="POST" action="@formAction/requests/changeavailability" id="change{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" /> <input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
{{#if_eq available true}} {{#if_eq available true}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> Mark Unavailable</button> <button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> @UI.Requests_MarkUnavailable</button>
{{else}} {{else}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> Mark Available</button> <button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> @UI.Requests_MarkAvailable</button>
{{/if_eq}} {{/if_eq}}
</form> </form>
@ -230,19 +231,15 @@
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" /> <input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<div class="dropdown"> <div class="dropdown">
<button id="{{requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button id="{{requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue <i class="fa fa-plus"></i> @UI.Search_ReportIssue
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{requestId}}" issue-select="0" class="dropdownIssue" href="#">Wrong Audio</a></li> <li><a id="{{requestId}}" issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li>
<li><a id="{{requestId}}" issue-select="1" class="dropdownIssue" href="#">No Subtitles</a></li> <li><a id="{{requestId}}" issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li>
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li> <li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li>
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li> <li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li>
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li> <li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
{{#if_eq admin true}}
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
{{/if_eq}}
</ul> </ul>
</div> </div>
</form> </form>
@ -274,9 +271,9 @@
<span class="label label-success">{{status}}</span> <span class="label label-success">{{status}}</span>
</div> </div>
<br /> <br />
<div>Release Date: {{releaseDate}}</div> <div>@UI.Requests_ReleaseDate {{releaseDate}}</div>
<div> <div>
Approved: @UI.Common_Approved:
{{#if_eq approved false}} {{#if_eq approved false}}
<i id="{{requestId}}notapproved" class="fa fa-times"></i> <i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}} {{/if_eq}}
@ -285,7 +282,7 @@
{{/if_eq}} {{/if_eq}}
</div> </div>
<div> <div>
Available @UI.Requests_Available
{{#if_eq available false}} {{#if_eq available false}}
<i id="availableIcon{{requestId}}" class="fa fa-times"></i> <i id="availableIcon{{requestId}}" class="fa fa-times"></i>
{{/if_eq}} {{/if_eq}}
@ -294,41 +291,29 @@
{{/if_eq}} {{/if_eq}}
</div> </div>
{{#if requestedUsers}} {{#if requestedUsers}}
<div>Requested By: {{requestedUsers}}</div> <div>@UI.Requests_RequestedBy: {{requestedUsers}}</div>
{{/if}} {{/if}}
<div>Requested Date: {{requestedDate}}</div> <div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
<div id="issueArea{{requestId}}">
{{#if otherMessage}}
<div>Message: {{otherMessage}}</div>
{{else}}
<div>Issue: {{issues}}</div>
{{/if}}
</div>
<div id="adminNotesArea{{requestId}}">
{{#if adminNote}}
<div>Note from Admin: {{adminNote}}</div>
{{/if}}
</div>
</div> </div>
<div class="col-sm-2 col-sm-push-3"> <div class="col-sm-2 col-sm-push-3">
{{#if_eq admin true}} {{#if_eq admin true}}
{{#if_eq approved false}} {{#if_eq approved false}}
<form method="POST" action="@formAction/approval/approve" id="approve{{requestId}}"> <form method="POST" action="@formAction/approval/approve" id="approve{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" /> <input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button> <button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> @UI.Common_Approve</button>
</form> </form>
{{/if_eq}} {{/if_eq}}
<form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}"> <form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" /> <input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> Remove</button> <button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> @UI.Common_Remove</button>
</form> </form>
<form method="POST" action="@formAction/requests/changeavailability" id="change{{requestId}}"> <form method="POST" action="@formAction/requests/changeavailability" id="change{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" /> <input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
{{#if_eq available true}} {{#if_eq available true}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> Mark Unavailable</button> <button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> @UI.Requests_MarkUnavailable</button>
{{else}} {{else}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> Mark Available</button> <button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> @UI.Requests_MarkAvailable</button>
{{/if_eq}} {{/if_eq}}
</form> </form>
@ -338,19 +323,15 @@
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" /> <input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<div class="dropdown"> <div class="dropdown">
<button id="{{requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button id="{{requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue <i class="fa fa-plus"></i> @UI.Search_ReportIssue
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{requestId}}" issue-select="0" class="dropdownIssue" href="#">Wrong Audio</a></li> <li><a id="{{requestId}}" issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li>
<li><a id="{{requestId}}" issue-select="1" class="dropdownIssue" href="#">No Subtitles</a></li> <li><a id="{{requestId}}" issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li>
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li> <li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li>
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li> <li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li>
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li> <li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
{{#if_eq admin true}}
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
{{/if_eq}}
</ul> </ul>
</div> </div>
</form> </form>
@ -366,37 +347,16 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h4 class="modal-title">Add issue/comment</h4> <h4 class="modal-title">@UI.Issues_Modal_Title</h4>
</div> </div>
<form method="POST" action="@formAction/issues/issuecomment" id="commentForm"> <form method="POST" action="@formAction/issues/issuecomment" id="commentForm">
<div class="modal-body"> <div class="modal-body">
<input name="requestId" class="requestId" type="text" hidden="hidden" value="" /> <input name="providerId" class="providerId" type="text" hidden="hidden" value="" />
<textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea> <textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">Close</button> <button type="button" class="btn btn-danger-outline" data-dismiss="modal">@UI.Common_Close</button>
<button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">Save changes</button> <button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">@UI.Common_Save</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="noteModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h4 class="modal-title">Add a note</h4>
</div>
<form method="POST" action="@formAction/requests/addnote" id="noteForm">
<div class="modal-body">
<input name="requestId" class="noteId" type="text" hidden="hidden" value="" />
<textarea class="form-control form-control-custom" rows="3" id="noteArea" name="noteArea"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary-outline theNoteSaveButton" data-dismiss="modal">Save changes</button>
</div> </div>
</form> </form>
</div> </div>

View file

@ -1,4 +1,5 @@
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@using PlexRequests.UI.Resources
@{ @{
var baseUrl = Html.GetBaseUrl(); var baseUrl = Html.GetBaseUrl();
var url = string.Empty; var url = string.Empty;
@ -8,8 +9,8 @@
} }
} }
<div> <div>
<h1>Search</h1> <h1>@UI.Search_Title</h1>
<h4>Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!</h4> <h4>@UI.Search_Paragraph</h4>
<br /> <br />
<!-- Nav tabs --> <!-- Nav tabs -->
@ -18,21 +19,21 @@
@if (Model.SearchForMovies) @if (Model.SearchForMovies)
{ {
<li role="presentation" class="active"> <li role="presentation" class="active">
<a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> Movies</a> <a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> @UI.Search_Movies</a>
</li> </li>
} }
@if (Model.SearchForTvShows) @if (Model.SearchForTvShows)
{ {
<li role="presentation"> <li role="presentation">
<a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i> TV Shows</a> <a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i> @UI.Search_TvShows</a>
</li> </li>
} }
@if (Model.SearchForMusic) @if (Model.SearchForMusic)
{ {
<li role="presentation"> <li role="presentation">
<a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i>Albums</a> <a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i>@UI.Search_Albums</a>
</li> </li>
} }
@ -54,12 +55,12 @@
<div class="input-group-addon"> <div class="input-group-addon">
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Suggestions @UI.Search_Suggestions
<i class="fa fa-chevron-down"></i> <i class="fa fa-chevron-down"></i>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a id="moviesComingSoon" href="#">Coming Soon</a></li> <li><a id="moviesComingSoon" href="#">@UI.Search_ComingSoon</a></li>
<li><a id="moviesInTheaters" href="#">In Theaters</a></li> <li><a id="moviesInTheaters" href="#">@UI.Search_InTheaters</a></li>
</ul> </ul>
</div> </div>
<i id="movieSearchButton" class="fa fa-search"></i> <i id="movieSearchButton" class="fa fa-search"></i>
@ -123,12 +124,12 @@
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="notifyUser" name="Notify"> <input type="checkbox" id="notifyUser" name="Notify">
<label for="notifyUser">Send me a notification when items I have requested have been added</label> <label for="notifyUser">@UI.Search_SendNotificationText</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button id="saveNotificationSettings" class="btn btn-primary-outline">Save</button> <button id="saveNotificationSettings" class="btn btn-primary-outline">@UI.Common_Save</button>
</div> </div>
</div> </div>
</fieldset> </fieldset>
@ -172,25 +173,25 @@
<form method="POST" action="@url/search/request/{{type}}" id="form{{id}}"> <form method="POST" action="@url/search/request/{{type}}" id="form{{id}}">
<input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" /> <input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" />
{{#if_eq available true}} {{#if_eq available true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button> <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button>
{{else}} {{else}}
{{#if_eq requested true}} {{#if_eq requested true}}
<button style="text-align: right" class="btn btn-primary-outline disabled" disabled><i class="fa fa-check"></i> Requested</button> <button style="text-align: right" class="btn btn-primary-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Requested</button>
{{else}} {{else}}
{{#if_eq type "movie"}} {{#if_eq type "movie"}}
<button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestMovie" type="submit"><i class="fa fa-plus"></i> Request</button> <button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestMovie" type="submit"><i class="fa fa-plus"></i> @UI.Search_Request</button>
{{/if_eq}} {{/if_eq}}
{{#if_eq type "tv"}} {{#if_eq type "tv"}}
<div class="dropdown"> <div class="dropdown">
<button id="{{id}}" class="btn btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button id="{{id}}" class="btn btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Request <i class="fa fa-plus"></i> @UI.Search_Request
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{id}}" season-select="0" class="dropdownTv " href="#">All Seasons</a></li> <li><a id="{{id}}" season-select="0" class="dropdownTv " href="#">@UI.Search_AllSeasons</a></li>
<li><a id="{{id}}" season-select="1" class="dropdownTv" href="#">First Season</a></li> <li><a id="{{id}}" season-select="1" class="dropdownTv" href="#">@UI.Search_FirstSeason</a></li>
<li><a id="{{id}}" season-select="2" class="dropdownTv" href="#">Latest Season</a></li> <li><a id="{{id}}" season-select="2" class="dropdownTv" href="#">@UI.Search_LatestSeason</a></li>
<li><a id="SeasonSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#seasonsModal" href="#">Select...</a></li> <li><a id="SeasonSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#seasonsModal" href="#">@UI.Search_SelectSeason...</a></li>
</ul> </ul>
</div> </div>
{{/if_eq}} {{/if_eq}}
@ -205,15 +206,15 @@
<input name="type" type="text" value="{{type}}" hidden="hidden" /> <input name="type" type="text" value="{{type}}" hidden="hidden" />
<div class="dropdown"> <div class="dropdown">
<button id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-exclamation"></i> Report Issue <i class="fa fa-exclamation"></i> @UI.Search_ReportIssue
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{id}}" issue-select="0" class="dropdownIssue" href="#">Wrong Audio</a></li> <li><a id="{{id}}" issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li>
<li><a id="{{id}}" issue-select="1" class="dropdownIssue" href="#">No Subtitles</a></li> <li><a id="{{id}}" issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li>
<li><a id="{{id}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li> <li><a id="{{id}}" issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li>
<li><a id="{{id}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li> <li><a id="{{id}}" issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li>
<li><a id="{{id}}" issue-select="4" class="dropdownIssue" data-identifier="{{id}}" data-type="{{type}}" href="#" data-toggle="modal" data-target="#issuesModal">Other</a></li> <li><a id="{{id}}" issue-select="4" class="dropdownIssue" data-identifier="{{id}}" data-type="{{type}}" href="#" data-toggle="modal" data-target="#issuesModal">@UI.Issues_Other</a></li>
</ul> </ul>
</div> </div>
</form> </form>
@ -251,17 +252,17 @@
<form method="POST" action="@url/search/request/{{type}}" id="form{{id}}"> <form method="POST" action="@url/search/request/{{type}}" id="form{{id}}">
<input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" /> <input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" />
{{#if_eq available true}} {{#if_eq available true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button> <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button>
{{else}} {{else}}
{{#if_eq requested true}} {{#if_eq requested true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Requested</button> <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Requested</button>
{{else}} {{else}}
<button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestAlbum" type="submit"><i class="fa fa-plus"></i> Request</button> <button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestAlbum" type="submit"><i class="fa fa-plus"></i> @UI.Search_Request</button>
{{/if_eq}} {{/if_eq}}
{{/if_eq}} {{/if_eq}}
<br /> <br />
<small class="row">Track Count: {{trackCount}}</small> <small class="row">@UI.Search_TrackCount: {{trackCount}}</small>
<small class="row">Country: {{country}}</small> <small class="row">@UI.Search_Country: {{country}}</small>
</form> </form>
</div> </div>
@ -275,7 +276,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Seasons</h4> <h4 class="modal-title">@UI.Search_Modal_SeasonsTitle</h4>
</div> </div>
<div class="modal-body" id="seasonsBody"> <div class="modal-body" id="seasonsBody">
@ -283,8 +284,8 @@
<div hidden="hidden" id="selectedSeasonsId"></div> <div hidden="hidden" id="selectedSeasonsId"></div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal">@UI.Common_Close</button>
<button type="button" id="seasonsRequest" class="btn btn-primary">Request</button> <button type="button" id="seasonsRequest" class="btn btn-primary">@UI.Search_Request</button>
</div> </div>
</div> </div>
</div> </div>
@ -296,7 +297,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h4 class="modal-title">Add an issue</h4> <h4 class="modal-title">@UI.Issues_Modal_Title</h4>
</div> </div>
<form method="POST" action="@url/issues/nonrequestissuecomment" id="commentForm"> <form method="POST" action="@url/issues/nonrequestissuecomment" id="commentForm">
<div class="modal-body"> <div class="modal-body">
@ -306,8 +307,8 @@
<textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea> <textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">Close</button> <button type="button" class="btn btn-danger-outline" data-dismiss="modal">@UI.Common_Close</button>
<button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">Save changes</button> <button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">@UI.Issues_Modal_Save</button>
</div> </div>
</form> </form>
</div> </div>
@ -317,7 +318,7 @@
<script id="seasons-template" type="text/x-handlebars-template"> <script id="seasons-template" type="text/x-handlebars-template">
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" class="selectedSeasons" id="{{id}}" name="{{id}}"><label for="{{id}}">Season {{id}}</label> <input type="checkbox" class="selectedSeasons" id="{{id}}" name="{{id}}"><label for="{{id}}">@UI.Search_Season {{id}}</label>
</div> </div>
</div> </div>

View file

@ -17,7 +17,7 @@
<title>Plex Requests</title> <title>Plex Requests</title>
<!-- Styles --> <!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@Html.LoadAnalytics()
@Html.LoadAssets() @Html.LoadAssets()
</head> </head>
<body> <body>

View file

@ -2,6 +2,7 @@
@using Nancy.Session @using Nancy.Session
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models @using PlexRequests.UI.Models
@using PlexRequests.UI.Resources
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{ @{
var baseUrl = Html.GetBaseUrl(); var baseUrl = Html.GetBaseUrl();
@ -14,7 +15,7 @@
<html> <html>
<div hidden="hidden" id="baseUrl">@baseUrl.ToHtmlString()</div> <div hidden="hidden" id="baseUrl">@baseUrl.ToHtmlString()</div>
<head> <head>
<title>Plex Requests</title> <title>@UI.Layout_Title</title>
<!-- Styles --> <!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@Html.LoadAnalytics() @Html.LoadAnalytics()
@ -32,42 +33,67 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="@url/search">Plex Requests</a> <a class="navbar-brand" href="@url/search">@UI.Layout_Title</a>
</div> </div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
@Html.GetNavbarUrl(Context, "/search", "Search", "search") @Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search")
@Html.GetNavbarUrl(Context, "/requests", "Requests", "plus-circle") @Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle")
@Html.GetNavbarUrl(Context, "/issues", "Issues", "exclamation", "<span id=\"issueCount\"></span>") @Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "<span id=\"issueCount\"></span>")
@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{
<li><a id="donate" onclick="donateClick('https://www.paypal.me/PlexRequestsNet');"><i class="fa fa-heart" style="color: red"></i> @UI.Layout_Donate</a></li>
}
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
@if (!Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin @if (!Context.CurrentUser.IsAuthenticated() && Context.Request.Session[SessionKeys.UsernameKey] == null) // TODO replace with IsAdmin
{ {
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> Admin</a></li> <li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> @UI.Layout_Admin</a></li>
} }
else @if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{ {
<li><a>@UI.Layout_Welcome @Context.Request.Session[SessionKeys.UsernameKey]</a></li>
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> Admin <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> @UI.Layout_Admin <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a href="@url/admin"><i class="fa fa-cog"></i> Settings</a></li> <li><a href="@url/admin"><i class="fa fa-cog"></i> @UI.Layout_Settings</a></li>
<li><a href="@url/changepassword"><i class="fa fa-key"></i> Change password</a></li> <li><a href="@url/changepassword"><i class="fa fa-key"></i> @UI.Layout_ChangePassword</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="https://www.paypal.me/PlexRequestsNet" target="_blank"><i class="fa fa-heart" style="color:red"></i> Donate!</a></li> <li><a href="@url/logout"><i class="fa fa-sign-out"></i> @UI.Layout_Logout</a></li>
<li class="divider"></li>
<li><a href="@url/logout"><i class="fa fa-sign-out"></i> Logout</a></li>
</ul> </ul>
</li> </li>
} }
@if (Context.Request.Session[SessionKeys.UsernameKey] != null) @if (Context.Request.Session[SessionKeys.UsernameKey] != null && !Context.CurrentUser.IsAuthenticated())
{ {
<li><a href="@url/userlogin/logout"><i class="fa fa-sign-out"></i> Logout</a></li> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> @UI.Layout_Welcome @Context.Request.Session[SessionKeys.UsernameKey] <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> @UI.Layout_Admin</a></li>
<li class="divider"></li>
<li><a href="@url/userlogin/logout"><i class="fa fa-sign-out"></i> @UI.Layout_Logout</a></li>
</ul>
</li>
} }
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-language" aria-hidden="true"><span class="caret"></span></i></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/culture?l=en&u=@Context.Request.Path">@UI.Layout_English</a></li>
<li><a href="@url/culture?l=nl&u=@Context.Request.Path">@UI.Layout_Dutch</a></li>
<li><a href="@url/culture?l=es&u=@Context.Request.Path">@UI.Layout_Spanish</a></li>
<li><a href="@url/culture?l=de&u=@Context.Request.Path">@UI.Layout_German</a></li>
<li><a href="@url/culture?l=da&u=@Context.Request.Path">@UI.Layout_Danish</a></li>
<li><a href="@url/culture?l=pt&u=@Context.Request.Path">@UI.Layout_Portuguese</a></li>
<li><a href="@url/culture?l=sv&u=@Context.Request.Path">@UI.Layout_Swedish</a></li>
<li><a href="@url/culture?l=it&u=@Context.Request.Path">@UI.Layout_Italian</a></li>
</ul>
<li />
</ul> </ul>
</div> </div>
</div> </div>
@ -86,6 +112,12 @@
</html> </html>
<script> <script>
function donateClick(url) {
ga('send', 'event', 'Navbar', 'Donate', 'Donate Clicked');
var redirectWindow = window.open(url, '_blank');
redirectWindow.location;
}
$(function () { $(function () {
var urlBase = '@Html.GetBaseUrl()'; var urlBase = '@Html.GetBaseUrl()';
@ -99,7 +131,7 @@
success: function (response) { success: function (response) {
if (response.updateAvailable) { if (response.updateAvailable) {
var status = createBaseUrl(urlBase, '/admin/status'); var status = createBaseUrl(urlBase, '/admin/status');
$('#updateAvailable').html("<i class='fa fa-cloud-download' aria-hidden='true'></i> There is a new update available! Click <a style='color: white' href='" + status + "'>Here!</a>"); $('#updateAvailable').html("<i class='fa fa-cloud-download' aria-hidden='true'></i> @UI.Layout_UpdateAvailablePart1 <a style='color: white' href='" + status + "'>@UI.Layout_UpdateAvailablePart2</a>");
$('#updateAvailable').removeAttr("hidden"); $('#updateAvailable').removeAttr("hidden");
$('body').addClass('update-available'); $('body').addClass('update-available');
} }
@ -125,6 +157,8 @@
// End Scroller // End Scroller
// Get Issue count // Get Issue count
var issueUrl = createBaseUrl(urlBase, '/issues/issuecount'); var issueUrl = createBaseUrl(urlBase, '/issues/issuecount');
$.ajax({ $.ajax({
@ -133,9 +167,9 @@
dataType: "json", dataType: "json",
success: function (response) { success: function (response) {
if (response) { if (response) {
if(response > 0) if (response > 0)
$('#issueCount').addClass("badge"); $('#issueCount').addClass("badge");
$('#issueCount').html(+response); $('#issueCount').html(+response);
} }
}, },
error: function (e) { error: function (e) {

View file

@ -1,19 +1,19 @@
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@using PlexRequests.UI.Resources
<div class="home"> <div class="home">
<h1>Login</h1> <h1>@UI.UserLogin_Title</h1>
<div> <div>
<p> <p>
Want to watch a movie or tv show but it's not currently on Plex? @UI.UserLogin_Paragraph <span title="@UI.UserLogin_Paragraph_SpanHover"><i class="fa fa-question-circle"></i></span>
Login below with your Plex.tv username and password! <span title="Your login details are only used to authenticate your Plex account."><i class="fa fa-question-circle"></i></span>
</p> </p>
</div> </div>
<form method="POST" id="loginForm"> <form method="POST" id="loginForm">
<div> <div>
<div> <div>
<label>Plex.tv Username </label> <label>@UI.UserLogin_Username</label>
</div> </div>
<div> <div>
<input class="form-control form-control-custom" type="text" name="Username" placeholder="Username" /> <input class="form-control form-control-custom" type="text" name="Username" placeholder="@UI.UserLogin_Username_Placeholder" />
</div> </div>
</div> </div>
<br /> <br />
@ -21,16 +21,16 @@
{ {
<div> <div>
<div> <div>
<label> Password </label> <label> @UI.UserLogin_Password </label>
</div> </div>
<div> <div>
<input class="form-control form-control-custom" name="Password" type="password" placeholder="Password"/> <input class="form-control form-control-custom" name="Password" type="password" placeholder="@UI.UserLogin_Password"/>
</div> </div>
</div> </div>
<br /> <br />
} }
<button id="loginBtn" class="btn btn-success-outline" type="submit"><i class="fa fa-user fa-fw"></i> Sign In</button> <button id="loginBtn" class="btn btn-success-outline" type="submit"><i class="fa fa-user fa-fw"></i> @UI.UserLogin_SignIn</button>
</form> </form>
</div> </div>
@ -62,7 +62,7 @@
}, },
error: function (e) { error: function (e) {
console.log(e); console.log(e);
generateNotify("Something went wrong!", "danger"); generateNotify("@UI.Javascript_SomethingWentWrong", "danger");
} }
}); });
}); });

View file

@ -99,4 +99,7 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_PrefixTranslations = False
EndGlobalSection
EndGlobal EndGlobal

View file

@ -1,5 +1,6 @@
# Plex Requests .NET!
![](http://i.imgur.com/s4nswSA.png?1)
____
[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex) [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex)
[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net) [![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net)
@ -24,14 +25,14 @@ I wanted to write a similar application in .Net!
* Headphones integration! * Headphones integration!
* Ability to run with a reverse proxy! * Ability to run with a reverse proxy!
# Preview () # Preview
![Preview](http://i.imgur.com/DgwkIsW.gif) ![Preview](http://i.imgur.com/DgwkIsW.gif)
#Installation #Installation
Download the latest [Release](https://github.com/tidusjar/PlexRequests.Net/releases).
Extract the .zip file (Unblock if on Windows! Right Click > Properties > Unblock). [Windows Guide!](http://www.htpcguides.com/install-plex-requests-net-windows-system-service/)
Just run `PlexRequests.exe`! (Mono compatible `mono PlexRequests.exe`) [Ubuntu Guide!](http://www.htpcguides.com/install-plex-requests-net-ubuntu-14-x/)
# FAQ # FAQ
Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ) Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ)

View file

@ -3,9 +3,9 @@ configuration: Release
assembly_info: assembly_info:
patch: true patch: true
file: '**\AssemblyInfo.*' file: '**\AssemblyInfo.*'
assembly_version: '1.8.0' assembly_version: '1.8.3'
assembly_file_version: '{version}' assembly_file_version: '{version}'
assembly_informational_version: '1.8.0' assembly_informational_version: '1.8.3'
before_build: before_build:
- cmd: appveyor-retry nuget restore - cmd: appveyor-retry nuget restore
build: build: