mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
Merge branch 'dev' of https://github.com/tidusjar/PlexRequests.Net into newdev
This commit is contained in:
commit
208c45340b
81 changed files with 3310 additions and 396 deletions
22
.github/ISSUE_TEMPLATE.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
If this is a bug report please make sure you have filled the following in:
|
||||||
|
(If it's not a bug and a feature request then just remove the below)
|
||||||
|
|
||||||
|
#### Plex Requests.Net Version:
|
||||||
|
|
||||||
|
|
||||||
|
#### Operating System:
|
||||||
|
|
||||||
|
|
||||||
|
#### Mono Version:
|
||||||
|
|
||||||
|
|
||||||
|
#### Applicable Logs (from `/logs/` directory or the Admin page):
|
||||||
|
|
||||||
|
```
|
||||||
|
Logs go here (Please make sure you remove any personal information from the logs)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Reproduction Steps:
|
||||||
|
|
||||||
|
Please include any steps to reproduce the issue, this the request that is causing the problem etc.
|
35
PlexRequests.Api.Interfaces/IHeadphonesApi.cs
Normal file
35
PlexRequests.Api.Interfaces/IHeadphonesApi.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: IHeadphonesApi.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;
|
||||||
|
|
||||||
|
namespace PlexRequests.Api.Interfaces
|
||||||
|
{
|
||||||
|
public interface IHeadphonesApi
|
||||||
|
{
|
||||||
|
bool AddAlbum(string apiKey, Uri baseUrl, string albumId);
|
||||||
|
}
|
||||||
|
}
|
37
PlexRequests.Api.Interfaces/IMusicBrainzApi.cs
Normal file
37
PlexRequests.Api.Interfaces/IMusicBrainzApi.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: IMusicBrainzApi.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 PlexRequests.Api.Models.Music;
|
||||||
|
|
||||||
|
namespace PlexRequests.Api.Interfaces
|
||||||
|
{
|
||||||
|
public interface IMusicBrainzApi
|
||||||
|
{
|
||||||
|
MusicBrainzSearchResults SearchAlbum(string searchTerm);
|
||||||
|
MusicBrainzCoverArt GetCoverArt(string releaseId);
|
||||||
|
MusicBrainzReleaseInfo GetAlbum(string releaseId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,8 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="IApiRequest.cs" />
|
<Compile Include="IApiRequest.cs" />
|
||||||
<Compile Include="ICouchPotatoApi.cs" />
|
<Compile Include="ICouchPotatoApi.cs" />
|
||||||
|
<Compile Include="IHeadphonesApi.cs" />
|
||||||
|
<Compile Include="IMusicBrainzApi.cs" />
|
||||||
<Compile Include="IPlexApi.cs" />
|
<Compile Include="IPlexApi.cs" />
|
||||||
<Compile Include="IPushbulletApi.cs" />
|
<Compile Include="IPushbulletApi.cs" />
|
||||||
<Compile Include="IPushoverApi.cs" />
|
<Compile Include="IPushoverApi.cs" />
|
||||||
|
|
45
PlexRequests.Api.Models/Music/HeadphonesAlbumSearchResult.cs
Normal file
45
PlexRequests.Api.Models/Music/HeadphonesAlbumSearchResult.cs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: HeadphonesAlbumSearchResult.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
namespace PlexRequests.Api.Models.Music
|
||||||
|
{
|
||||||
|
public class HeadphonesAlbumSearchResult
|
||||||
|
{
|
||||||
|
public string rgid { get; set; }
|
||||||
|
public string albumurl { get; set; }
|
||||||
|
public string tracks { get; set; }
|
||||||
|
public string date { get; set; }
|
||||||
|
public string id { get; set; } // Artist ID
|
||||||
|
public string rgtype { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public string url { get; set; }
|
||||||
|
public string country { get; set; }
|
||||||
|
public string albumid { get; set; } // AlbumId
|
||||||
|
public int score { get; set; }
|
||||||
|
public string uniquename { get; set; }
|
||||||
|
public string formats { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: HeadphonesSearchResult.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
namespace PlexRequests.Api.Models.Music
|
||||||
|
{
|
||||||
|
public class HeadphonesArtistSearchResult
|
||||||
|
{
|
||||||
|
public string url { get; set; } // MusicBrainz url
|
||||||
|
public int score { get; set; } // Search Match score?
|
||||||
|
public string name { get; set; } // Artist Name
|
||||||
|
public string uniquename { get; set; } // Artist Unique Name
|
||||||
|
public string id { get; set; } // Artist Unique ID for MusicBrainz
|
||||||
|
}
|
||||||
|
}
|
55
PlexRequests.Api.Models/Music/MusicBrainzCoverArt.cs
Normal file
55
PlexRequests.Api.Models/Music/MusicBrainzCoverArt.cs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: MusicBrainzCoverArt.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace PlexRequests.Api.Models.Music
|
||||||
|
{
|
||||||
|
public class Thumbnails
|
||||||
|
{
|
||||||
|
public string large { get; set; }
|
||||||
|
public string small { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Image
|
||||||
|
{
|
||||||
|
public List<string> types { get; set; }
|
||||||
|
public bool front { get; set; }
|
||||||
|
public bool back { get; set; }
|
||||||
|
public int edit { get; set; }
|
||||||
|
public string image { get; set; }
|
||||||
|
public string comment { get; set; }
|
||||||
|
public bool approved { get; set; }
|
||||||
|
public string id { get; set; }
|
||||||
|
public Thumbnails thumbnails { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MusicBrainzCoverArt
|
||||||
|
{
|
||||||
|
public List<Image> images { get; set; }
|
||||||
|
public string release { get; set; }
|
||||||
|
}
|
||||||
|
}
|
66
PlexRequests.Api.Models/Music/MusicBrainzReleaseInfo.cs
Normal file
66
PlexRequests.Api.Models/Music/MusicBrainzReleaseInfo.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: MusicBrainzReleaseInfo.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 Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace PlexRequests.Api.Models.Music
|
||||||
|
{
|
||||||
|
public class CoverArtArchive
|
||||||
|
{
|
||||||
|
public int count { get; set; }
|
||||||
|
public bool back { get; set; }
|
||||||
|
public bool artwork { get; set; }
|
||||||
|
public bool front { get; set; }
|
||||||
|
public bool darkened { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class MusicBrainzReleaseInfo
|
||||||
|
{
|
||||||
|
public string date { get; set; }
|
||||||
|
public string status { get; set; }
|
||||||
|
public string asin { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public string quality { get; set; }
|
||||||
|
public string country { get; set; }
|
||||||
|
public string packaging { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "text-representation")]
|
||||||
|
public TextRepresentation TextRepresentation { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "cover-art-archive")]
|
||||||
|
public CoverArtArchive CoverArtArchive { get; set; }
|
||||||
|
public string barcode { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "release-events")]
|
||||||
|
public List<ReleaseEvent> ReleaseRvents { get; set; }
|
||||||
|
public string id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
152
PlexRequests.Api.Models/Music/MusicBrainzSearchResults.cs
Normal file
152
PlexRequests.Api.Models/Music/MusicBrainzSearchResults.cs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: MusicBrainzSearchResults.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 Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace PlexRequests.Api.Models.Music
|
||||||
|
{
|
||||||
|
public class TextRepresentation
|
||||||
|
{
|
||||||
|
public string language { get; set; }
|
||||||
|
public string script { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Alias
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "sort-name")]
|
||||||
|
public string SortName { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public object locale { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public object primary { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "begin-date")]
|
||||||
|
public object BeginDate { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "end-date")]
|
||||||
|
public object EndDate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Artist
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "sort-date")]
|
||||||
|
public string SortName { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public List<Alias> aliases { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ArtistCredit
|
||||||
|
{
|
||||||
|
public Artist artist { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReleaseGroup
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "primary-type")]
|
||||||
|
public string PrimaryType { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "secondary-types")]
|
||||||
|
public List<string> SecondaryTypes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Area
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "sort-name")]
|
||||||
|
public string SortName { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "iso-3166-1-codes")]
|
||||||
|
public List<string> ISO31661Codes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReleaseEvent
|
||||||
|
{
|
||||||
|
public string date { get; set; }
|
||||||
|
public Area area { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Label
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LabelInfo
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "catalog-number")]
|
||||||
|
public string CatalogNumber { get; set; }
|
||||||
|
public Label label { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Medium
|
||||||
|
{
|
||||||
|
public string format { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "disc-count")]
|
||||||
|
public int DiscCount { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "catalog-number")]
|
||||||
|
public int CatalogNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Release
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string score { get; set; }
|
||||||
|
public int count { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public string status { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public string packaging { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "text-representation")]
|
||||||
|
public TextRepresentation TextRepresentation { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "artist-credit")]
|
||||||
|
public List<ArtistCredit> ArtistCredit { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "release-group")]
|
||||||
|
public ReleaseGroup ReleaseGroup { get; set; }
|
||||||
|
public string date { get; set; }
|
||||||
|
public string country { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "release-events")]
|
||||||
|
public List<ReleaseEvent> ReleaseEvents { get; set; }
|
||||||
|
public string barcode { get; set; }
|
||||||
|
public string asin { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "label-info")]
|
||||||
|
public List<LabelInfo> LabelInfo { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "track-count")]
|
||||||
|
public int TrackCount { get; set; }
|
||||||
|
public List<Medium> media { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MusicBrainzSearchResults
|
||||||
|
{
|
||||||
|
public string created { get; set; }
|
||||||
|
public int count { get; set; }
|
||||||
|
public int offset { get; set; }
|
||||||
|
public List<Release> releases { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -48,6 +48,11 @@
|
||||||
<Compile Include="Movie\CouchPotatoAdd.cs" />
|
<Compile Include="Movie\CouchPotatoAdd.cs" />
|
||||||
<Compile Include="Movie\CouchPotatoProfiles.cs" />
|
<Compile Include="Movie\CouchPotatoProfiles.cs" />
|
||||||
<Compile Include="Movie\CouchPotatoStatus.cs" />
|
<Compile Include="Movie\CouchPotatoStatus.cs" />
|
||||||
|
<Compile Include="Music\HeadphonesAlbumSearchResult.cs" />
|
||||||
|
<Compile Include="Music\HeadphonesArtistSearchResult.cs" />
|
||||||
|
<Compile Include="Music\MusicBrainzCoverArt.cs" />
|
||||||
|
<Compile Include="Music\MusicBrainzReleaseInfo.cs" />
|
||||||
|
<Compile Include="Music\MusicBrainzSearchResults.cs" />
|
||||||
<Compile Include="Notifications\PushbulletPush.cs" />
|
<Compile Include="Notifications\PushbulletPush.cs" />
|
||||||
<Compile Include="Notifications\PushbulletResponse.cs" />
|
<Compile Include="Notifications\PushbulletResponse.cs" />
|
||||||
<Compile Include="Notifications\PushoverResponse.cs" />
|
<Compile Include="Notifications\PushoverResponse.cs" />
|
||||||
|
@ -66,6 +71,7 @@
|
||||||
<Compile Include="SickRage\SickRageStatus.cs" />
|
<Compile Include="SickRage\SickRageStatus.cs" />
|
||||||
<Compile Include="SickRage\SickRageTvAdd.cs" />
|
<Compile Include="SickRage\SickRageTvAdd.cs" />
|
||||||
<Compile Include="Sonarr\SonarrAddSeries.cs" />
|
<Compile Include="Sonarr\SonarrAddSeries.cs" />
|
||||||
|
<Compile Include="Sonarr\SonarrError.cs" />
|
||||||
<Compile Include="Sonarr\SonarrProfile.cs" />
|
<Compile Include="Sonarr\SonarrProfile.cs" />
|
||||||
<Compile Include="Sonarr\SystemStatus.cs" />
|
<Compile Include="Sonarr\SystemStatus.cs" />
|
||||||
<Compile Include="Tv\Authentication.cs" />
|
<Compile Include="Tv\Authentication.cs" />
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace PlexRequests.Api.Models.Sonarr
|
namespace PlexRequests.Api.Models.Sonarr
|
||||||
{
|
{
|
||||||
public class Season
|
public class Season
|
||||||
|
@ -23,6 +25,8 @@ namespace PlexRequests.Api.Models.Sonarr
|
||||||
public string imdbId { get; set; }
|
public string imdbId { get; set; }
|
||||||
public string titleSlug { get; set; }
|
public string titleSlug { get; set; }
|
||||||
public int id { get; set; }
|
public int id { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AddOptions
|
public class AddOptions
|
||||||
|
|
36
PlexRequests.Api.Models/Sonarr/SonarrError.cs
Normal file
36
PlexRequests.Api.Models/Sonarr/SonarrError.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: SonarrError.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
namespace PlexRequests.Api.Models.Sonarr
|
||||||
|
{
|
||||||
|
public class SonarrError
|
||||||
|
{
|
||||||
|
public string propertyName { get; set; }
|
||||||
|
public string errorMessage { get; set; }
|
||||||
|
public string attemptedValue { get; set; }
|
||||||
|
public string[] formattedMessageArguments { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,13 +26,9 @@
|
||||||
#endregion
|
#endregion
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
|
@ -96,20 +92,13 @@ namespace PlexRequests.Api
|
||||||
throw new ApplicationException(message, response.ErrorException);
|
throw new ApplicationException(message, response.ErrorException);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
var json = JsonConvert.DeserializeObject<T>(response.Content);
|
||||||
var json = JsonConvert.DeserializeObject<T>(response.Content);
|
|
||||||
return json;
|
return json;
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Fatal(e);
|
|
||||||
Log.Info(response.Content);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public T DeserializeXml<T>(string input)
|
private T DeserializeXml<T>(string input)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
var ser = new XmlSerializer(typeof(T));
|
var ser = new XmlSerializer(typeof(T));
|
||||||
|
|
74
PlexRequests.Api/HeadphonesApi.cs
Normal file
74
PlexRequests.Api/HeadphonesApi.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: HeadphonesApi.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 Newtonsoft.Json;
|
||||||
|
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
using PlexRequests.Api.Interfaces;
|
||||||
|
using PlexRequests.Api.Models.Music;
|
||||||
|
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace PlexRequests.Api
|
||||||
|
{
|
||||||
|
public class HeadphonesApi : IHeadphonesApi
|
||||||
|
{
|
||||||
|
public HeadphonesApi()
|
||||||
|
{
|
||||||
|
Api = new ApiRequest();
|
||||||
|
}
|
||||||
|
private ApiRequest Api { get; }
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public bool AddAlbum(string apiKey, Uri baseUrl, string albumId)
|
||||||
|
{
|
||||||
|
Log.Trace("Adding album: {0}", albumId);
|
||||||
|
var request = new RestRequest
|
||||||
|
{
|
||||||
|
Resource = "/api?cmd=addAlbum&id={albumId}",
|
||||||
|
Method = Method.GET
|
||||||
|
};
|
||||||
|
|
||||||
|
request.AddQueryParameter("apikey", apiKey);
|
||||||
|
request.AddUrlSegment("albumId", albumId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//var result = Api.Execute<string>(request, baseUrl);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (JsonSerializationException jse)
|
||||||
|
{
|
||||||
|
Log.Warn(jse);
|
||||||
|
return false; // If there is no matching result we do not get returned a JSON string, it just returns "false".
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
PlexRequests.Api/MusicBrainzApi.cs
Normal file
114
PlexRequests.Api/MusicBrainzApi.cs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: MusicBrainzApi.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 Newtonsoft.Json;
|
||||||
|
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
using PlexRequests.Api.Interfaces;
|
||||||
|
using PlexRequests.Api.Models.Music;
|
||||||
|
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace PlexRequests.Api
|
||||||
|
{
|
||||||
|
public class MusicBrainzApi : IMusicBrainzApi
|
||||||
|
{
|
||||||
|
public MusicBrainzApi()
|
||||||
|
{
|
||||||
|
Api = new ApiRequest();
|
||||||
|
}
|
||||||
|
private ApiRequest Api { get; }
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
private readonly Uri BaseUri = new Uri("http://musicbrainz.org/ws/2/");
|
||||||
|
|
||||||
|
public MusicBrainzSearchResults SearchAlbum(string searchTerm)
|
||||||
|
{
|
||||||
|
Log.Trace("Searching for album: {0}", searchTerm);
|
||||||
|
var request = new RestRequest
|
||||||
|
{
|
||||||
|
Resource = "release/?query={searchTerm}&fmt=json",
|
||||||
|
Method = Method.GET
|
||||||
|
};
|
||||||
|
request.AddUrlSegment("searchTerm", searchTerm);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Api.ExecuteJson<MusicBrainzSearchResults>(request, BaseUri);
|
||||||
|
}
|
||||||
|
catch (JsonSerializationException jse)
|
||||||
|
{
|
||||||
|
Log.Warn(jse);
|
||||||
|
return new MusicBrainzSearchResults(); // If there is no matching result we do not get returned a JSON string, it just returns "false".
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MusicBrainzReleaseInfo GetAlbum(string releaseId)
|
||||||
|
{
|
||||||
|
Log.Trace("Getting album: {0}", releaseId);
|
||||||
|
var request = new RestRequest
|
||||||
|
{
|
||||||
|
Resource = "release/{albumId}?fmt=json",
|
||||||
|
Method = Method.GET
|
||||||
|
};
|
||||||
|
request.AddUrlSegment("albumId", releaseId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Api.Execute<MusicBrainzReleaseInfo>(request, BaseUri);
|
||||||
|
}
|
||||||
|
catch (JsonSerializationException jse)
|
||||||
|
{
|
||||||
|
Log.Warn(jse);
|
||||||
|
return new MusicBrainzReleaseInfo(); // If there is no matching result we do not get returned a JSON string, it just returns "false".
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MusicBrainzCoverArt GetCoverArt(string releaseId)
|
||||||
|
{
|
||||||
|
Log.Trace("Getting cover art for release: {0}", releaseId);
|
||||||
|
var request = new RestRequest
|
||||||
|
{
|
||||||
|
Resource = "release/{releaseId}",
|
||||||
|
Method = Method.GET
|
||||||
|
};
|
||||||
|
request.AddUrlSegment("releaseId", releaseId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Api.Execute<MusicBrainzCoverArt>(request, new Uri("http://coverartarchive.org/"));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Warn(e);
|
||||||
|
return new MusicBrainzCoverArt(); // If there is no matching result we do not get returned a JSON string, it just returns "false".
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,6 +66,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ApiRequest.cs" />
|
<Compile Include="ApiRequest.cs" />
|
||||||
|
<Compile Include="MusicBrainzApi.cs" />
|
||||||
<Compile Include="MockApiData.Designer.cs">
|
<Compile Include="MockApiData.Designer.cs">
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
|
@ -75,6 +76,7 @@
|
||||||
<Compile Include="PushoverApi.cs" />
|
<Compile Include="PushoverApi.cs" />
|
||||||
<Compile Include="PushbulletApi.cs" />
|
<Compile Include="PushbulletApi.cs" />
|
||||||
<Compile Include="SickrageApi.cs" />
|
<Compile Include="SickrageApi.cs" />
|
||||||
|
<Compile Include="HeadphonesApi.cs" />
|
||||||
<Compile Include="SonarrApi.cs" />
|
<Compile Include="SonarrApi.cs" />
|
||||||
<Compile Include="CouchPotatoApi.cs" />
|
<Compile Include="CouchPotatoApi.cs" />
|
||||||
<Compile Include="MovieBase.cs" />
|
<Compile Include="MovieBase.cs" />
|
||||||
|
|
|
@ -27,9 +27,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using PlexRequests.Api.Interfaces;
|
using PlexRequests.Api.Interfaces;
|
||||||
using PlexRequests.Api.Models.Sonarr;
|
using PlexRequests.Api.Models.Sonarr;
|
||||||
|
using PlexRequests.Helpers;
|
||||||
|
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
|
|
||||||
namespace PlexRequests.Api
|
namespace PlexRequests.Api
|
||||||
|
@ -56,7 +61,8 @@ namespace PlexRequests.Api
|
||||||
|
|
||||||
public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int seasonCount, int[] seasons, string apiKey, Uri baseUrl)
|
public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int seasonCount, int[] seasons, string apiKey, Uri baseUrl)
|
||||||
{
|
{
|
||||||
|
Log.Debug("Adding series {0}", title);
|
||||||
|
Log.Debug("Seasons = {0}, out of {1} seasons", seasons.DumpJson(), seasonCount);
|
||||||
var request = new RestRequest
|
var request = new RestRequest
|
||||||
{
|
{
|
||||||
Resource = "/api/Series?",
|
Resource = "/api/Series?",
|
||||||
|
@ -74,7 +80,6 @@ namespace PlexRequests.Api
|
||||||
rootFolderPath = rootPath
|
rootFolderPath = rootPath
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
for (var i = 1; i <= seasonCount; i++)
|
for (var i = 1; i <= seasonCount; i++)
|
||||||
{
|
{
|
||||||
var season = new Season
|
var season = new Season
|
||||||
|
@ -85,12 +90,25 @@ namespace PlexRequests.Api
|
||||||
options.seasons.Add(season);
|
options.seasons.Add(season);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Debug("Sonarr API Options:");
|
||||||
|
Log.Debug(options.DumpJson());
|
||||||
|
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
request.AddJsonBody(options);
|
request.AddJsonBody(options);
|
||||||
|
|
||||||
var obj = Api.ExecuteJson<SonarrAddSeries>(request, baseUrl);
|
SonarrAddSeries result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = Api.ExecuteJson<SonarrAddSeries>(request, baseUrl);
|
||||||
|
}
|
||||||
|
catch (JsonSerializationException jse)
|
||||||
|
{
|
||||||
|
Log.Error(jse);
|
||||||
|
var error = Api.ExecuteJson<SonarrError>(request, baseUrl);
|
||||||
|
result = new SonarrAddSeries { ErrorMessage = error.errorMessage };
|
||||||
|
}
|
||||||
|
|
||||||
return obj;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SystemStatus SystemStatus(string apiKey, Uri baseUrl)
|
public SystemStatus SystemStatus(string apiKey, Uri baseUrl)
|
||||||
|
|
|
@ -33,7 +33,9 @@ namespace PlexRequests.Core
|
||||||
public interface IRequestService
|
public interface IRequestService
|
||||||
{
|
{
|
||||||
long AddRequest(RequestedModel model);
|
long AddRequest(RequestedModel model);
|
||||||
bool CheckRequest(int providerId);
|
RequestedModel CheckRequest(int providerId);
|
||||||
|
RequestedModel CheckRequest(string musicId);
|
||||||
|
|
||||||
void DeleteRequest(RequestedModel request);
|
void DeleteRequest(RequestedModel request);
|
||||||
bool UpdateRequest(RequestedModel model);
|
bool UpdateRequest(RequestedModel model);
|
||||||
RequestedModel Get(int id);
|
RequestedModel Get(int id);
|
||||||
|
|
|
@ -52,16 +52,24 @@ namespace PlexRequests.Core
|
||||||
// TODO Keep an eye on this, since we are now doing 2 DB update for 1 single request, inserting and then updating
|
// TODO Keep an eye on this, since we are now doing 2 DB update for 1 single request, inserting and then updating
|
||||||
model.Id = (int)id;
|
model.Id = (int)id;
|
||||||
|
|
||||||
entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = (int)id };
|
entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = (int)id, MusicId = model.MusicBrainzId};
|
||||||
var result = Repo.Update(entity);
|
var result = Repo.Update(entity);
|
||||||
|
|
||||||
return result ? id : -1;
|
return result ? id : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckRequest(int providerId)
|
public RequestedModel CheckRequest(int providerId)
|
||||||
{
|
{
|
||||||
var blobs = Repo.GetAll();
|
var blobs = Repo.GetAll();
|
||||||
return blobs.Any(x => x.ProviderId == providerId);
|
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId);
|
||||||
|
return blob != null ? ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestedModel CheckRequest(string musicId)
|
||||||
|
{
|
||||||
|
var blobs = Repo.GetAll();
|
||||||
|
var blob = blobs.FirstOrDefault(x => x.MusicId == musicId);
|
||||||
|
return blob != null ? ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteRequest(RequestedModel request)
|
public void DeleteRequest(RequestedModel request)
|
||||||
|
@ -79,6 +87,10 @@ namespace PlexRequests.Core
|
||||||
public RequestedModel Get(int id)
|
public RequestedModel Get(int id)
|
||||||
{
|
{
|
||||||
var blob = Repo.Get(id);
|
var blob = Repo.Get(id);
|
||||||
|
if (blob == null)
|
||||||
|
{
|
||||||
|
return new RequestedModel();
|
||||||
|
}
|
||||||
var model = ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content);
|
var model = ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,10 @@
|
||||||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Octokit, Version=0.19.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="Octokit, Version=0.19.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\Octokit.0.19.0\lib\net45\Octokit.dll</HintPath>
|
<HintPath>..\packages\Octokit.0.19.0\lib\net45\Octokit.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
|
@ -74,6 +78,7 @@
|
||||||
<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\PushoverNotificationSettings.cs" />
|
<Compile Include="SettingModels\PushoverNotificationSettings.cs" />
|
||||||
<Compile Include="SettingModels\PushBulletNotificationSettings.cs" />
|
<Compile Include="SettingModels\PushBulletNotificationSettings.cs" />
|
||||||
<Compile Include="SettingModels\EmailNotificationSettings.cs" />
|
<Compile Include="SettingModels\EmailNotificationSettings.cs" />
|
||||||
|
|
58
PlexRequests.Core/SettingModels/HeadphonesSettings.cs
Normal file
58
PlexRequests.Core/SettingModels/HeadphonesSettings.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: CouchPotatoSettings.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 Newtonsoft.Json;
|
||||||
|
using PlexRequests.Helpers;
|
||||||
|
|
||||||
|
namespace PlexRequests.Core.SettingModels
|
||||||
|
{
|
||||||
|
public class HeadphonesSettings : Settings
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public string Ip { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
public bool Ssl { get; set; }
|
||||||
|
public string SubDir { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public Uri FullUri
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(SubDir))
|
||||||
|
{
|
||||||
|
var formattedSubDir = Ip.ReturnUriWithSubDir(Port, Ssl, SubDir);
|
||||||
|
return formattedSubDir;
|
||||||
|
}
|
||||||
|
var formatted = Ip.ReturnUri(Port, Ssl);
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,10 @@
|
||||||
// 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 Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace PlexRequests.Core.SettingModels
|
namespace PlexRequests.Core.SettingModels
|
||||||
{
|
{
|
||||||
public class PlexRequestSettings : Settings
|
public class PlexRequestSettings : Settings
|
||||||
|
@ -32,8 +36,33 @@ namespace PlexRequests.Core.SettingModels
|
||||||
|
|
||||||
public bool SearchForMovies { get; set; }
|
public bool SearchForMovies { get; set; }
|
||||||
public bool SearchForTvShows { get; set; }
|
public bool SearchForTvShows { get; set; }
|
||||||
|
public bool SearchForMusic { get; set; }
|
||||||
public bool RequireMovieApproval { get; set; }
|
public bool RequireMovieApproval { get; set; }
|
||||||
public bool RequireTvShowApproval { get; set; }
|
public bool RequireTvShowApproval { get; set; }
|
||||||
|
public bool RequireMusicApproval { get; set; }
|
||||||
|
public bool UsersCanViewOnlyOwnRequests { get; set; }
|
||||||
public int WeeklyRequestLimit { get; set; }
|
public int WeeklyRequestLimit { get; set; }
|
||||||
|
public string NoApprovalUsers { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<string> ApprovalWhiteList
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var users = new List<string>();
|
||||||
|
if (string.IsNullOrEmpty(NoApprovalUsers))
|
||||||
|
{
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var user in splitUsers)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(user))
|
||||||
|
users.Add(user.Trim());
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using Mono.Data.Sqlite;
|
using Mono.Data.Sqlite;
|
||||||
|
using NLog;
|
||||||
using PlexRequests.Api;
|
using PlexRequests.Api;
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
|
@ -40,6 +41,9 @@ namespace PlexRequests.Core
|
||||||
{
|
{
|
||||||
public class Setup
|
public class Setup
|
||||||
{
|
{
|
||||||
|
public const int SchemaVersion = 1;
|
||||||
|
|
||||||
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
private static DbConfiguration Db { get; set; }
|
private static DbConfiguration Db { get; set; }
|
||||||
public string SetupDb()
|
public string SetupDb()
|
||||||
{
|
{
|
||||||
|
@ -53,11 +57,40 @@ namespace PlexRequests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
MigrateDb();
|
MigrateDb();
|
||||||
|
CheckSchema();
|
||||||
return Db.DbConnection().ConnectionString;
|
return Db.DbConnection().ConnectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ConnectionString => Db.DbConnection().ConnectionString;
|
public static string ConnectionString => Db.DbConnection().ConnectionString;
|
||||||
|
|
||||||
|
|
||||||
|
private void CheckSchema()
|
||||||
|
{
|
||||||
|
var connection = Db.DbConnection();
|
||||||
|
var schema = connection.GetSchemaVersion();
|
||||||
|
if (schema == null)
|
||||||
|
{
|
||||||
|
connection.CreateSchema(); // Set the default.
|
||||||
|
schema = connection.GetSchemaVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
var version = schema.SchemaVersion;
|
||||||
|
if (version == 0)
|
||||||
|
{
|
||||||
|
connection.UpdateSchemaVersion(SchemaVersion);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TableCreation.AlterTable(Db.DbConnection(), "RequestBlobs", "ADD COLUMN", "MusicId", false, "TEXT");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("Tried updating the schema to version 1");
|
||||||
|
Log.Error(e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateDefaultSettingsPage()
|
private void CreateDefaultSettingsPage()
|
||||||
{
|
{
|
||||||
var defaultSettings = new PlexRequestSettings
|
var defaultSettings = new PlexRequestSettings
|
||||||
|
@ -72,8 +105,9 @@ namespace PlexRequests.Core
|
||||||
s.SaveSettings(defaultSettings);
|
s.SaveSettings(defaultSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MigrateDb() // TODO: Remove when no longer needed
|
private void MigrateDb() // TODO: Remove in v1.7
|
||||||
{
|
{
|
||||||
|
|
||||||
var result = new List<long>();
|
var result = new List<long>();
|
||||||
RequestedModel[] requestedModels;
|
RequestedModel[] requestedModels;
|
||||||
var repo = new GenericRepository<RequestedModel>(Db, new MemoryCacheProvider());
|
var repo = new GenericRepository<RequestedModel>(Db, new MemoryCacheProvider());
|
||||||
|
@ -121,7 +155,7 @@ namespace PlexRequests.Core
|
||||||
result.Add(id);
|
result.Add(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var source in requestedModels.Where(x => x.Type== RequestType.Movie))
|
foreach (var source in requestedModels.Where(x => x.Type == RequestType.Movie))
|
||||||
{
|
{
|
||||||
var id = jsonRepo.AddRequest(source);
|
var id = jsonRepo.AddRequest(source);
|
||||||
result.Add(id);
|
result.Add(id);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<package id="Nancy" version="1.4.3" targetFramework="net452" />
|
<package id="Nancy" version="1.4.3" targetFramework="net452" />
|
||||||
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" />
|
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" />
|
||||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" />
|
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" />
|
||||||
|
<package id="NLog" version="4.2.3" targetFramework="net46" />
|
||||||
<package id="Octokit" version="0.19.0" targetFramework="net46" />
|
<package id="Octokit" version="0.19.0" targetFramework="net46" />
|
||||||
<package id="valueinjecter" version="3.1.1.2" targetFramework="net452" />
|
<package id="valueinjecter" version="3.1.1.2" targetFramework="net452" />
|
||||||
</packages>
|
</packages>
|
26
PlexRequests.Helpers/DateTimeHelper.cs
Normal file
26
PlexRequests.Helpers/DateTimeHelper.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace PlexRequests.Helpers
|
||||||
|
{
|
||||||
|
public static class DateTimeHelper
|
||||||
|
{
|
||||||
|
public static DateTimeOffset OffsetUTCDateTime(DateTime utcDateTime, int minuteOffset)
|
||||||
|
{
|
||||||
|
//TimeSpan ts = TimeSpan.FromMinutes(-minuteOffset);
|
||||||
|
//return new DateTimeOffset(utcDateTime).ToOffset(ts);
|
||||||
|
|
||||||
|
// this is a workaround below to work with MONO
|
||||||
|
var tzi = FindTimeZoneFromOffset(minuteOffset);
|
||||||
|
var utcOffset = tzi.GetUtcOffset(utcDateTime);
|
||||||
|
var newDate = utcDateTime + utcOffset;
|
||||||
|
return new DateTimeOffset(newDate.Ticks, utcOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TimeZoneInfo FindTimeZoneFromOffset(int minuteOffset)
|
||||||
|
{
|
||||||
|
var tzc = TimeZoneInfo.GetSystemTimeZones();
|
||||||
|
return tzc.FirstOrDefault(x => x.BaseUtcOffset.TotalMinutes == -minuteOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="AssemblyHelper.cs" />
|
<Compile Include="AssemblyHelper.cs" />
|
||||||
<Compile Include="ByteConverterHelper.cs" />
|
<Compile Include="ByteConverterHelper.cs" />
|
||||||
|
<Compile Include="DateTimeHelper.cs" />
|
||||||
<Compile Include="Exceptions\ApplicationSettingsException.cs" />
|
<Compile Include="Exceptions\ApplicationSettingsException.cs" />
|
||||||
<Compile Include="HtmlRemover.cs" />
|
<Compile Include="HtmlRemover.cs" />
|
||||||
<Compile Include="ICacheProvider.cs" />
|
<Compile Include="ICacheProvider.cs" />
|
||||||
|
|
|
@ -32,12 +32,12 @@ using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
using PlexRequests.Api.Interfaces;
|
using PlexRequests.Api.Interfaces;
|
||||||
using PlexRequests.Api.Models;
|
|
||||||
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.Exceptions;
|
using PlexRequests.Helpers.Exceptions;
|
||||||
using PlexRequests.Services.Interfaces;
|
using PlexRequests.Services.Interfaces;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
|
||||||
namespace PlexRequests.Services.Tests
|
namespace PlexRequests.Services.Tests
|
||||||
{
|
{
|
||||||
|
@ -66,7 +66,7 @@ namespace PlexRequests.Services.Tests
|
||||||
var requestMock = new Mock<IRequestService>();
|
var requestMock = new Mock<IRequestService>();
|
||||||
var plexMock = new Mock<IPlexApi>();
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
|
||||||
var searchResult = new PlexSearch {Video = new List<Video> {new Video {Title = "title", Year = "2011"} } };
|
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "title", Year = "2011" } } };
|
||||||
|
|
||||||
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
||||||
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
@ -87,7 +87,7 @@ namespace PlexRequests.Services.Tests
|
||||||
var requestMock = new Mock<IRequestService>();
|
var requestMock = new Mock<IRequestService>();
|
||||||
var plexMock = new Mock<IPlexApi>();
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
|
||||||
var searchResult = new PlexSearch { Directory = new Directory1 {Title = "title", Year = "2013"} };
|
var searchResult = new PlexSearch { Directory = new Directory1 { Title = "title", Year = "2013" } };
|
||||||
|
|
||||||
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
||||||
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
@ -100,6 +100,27 @@ namespace PlexRequests.Services.Tests
|
||||||
Assert.That(result, Is.True);
|
Assert.That(result, Is.True);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsAvailableDirectoryTitleWithoutYearTest()
|
||||||
|
{
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
|
||||||
|
var searchResult = new PlexSearch { Directory = new Directory1 { Title = "title", } };
|
||||||
|
|
||||||
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
||||||
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(searchResult);
|
||||||
|
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
var result = Checker.IsAvailable("title", null);
|
||||||
|
|
||||||
|
Assert.That(result, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void IsNotAvailableTest()
|
public void IsNotAvailableTest()
|
||||||
{
|
{
|
||||||
|
@ -108,7 +129,7 @@ namespace PlexRequests.Services.Tests
|
||||||
var requestMock = new Mock<IRequestService>();
|
var requestMock = new Mock<IRequestService>();
|
||||||
var plexMock = new Mock<IPlexApi>();
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
|
||||||
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "wrong tistle", Year = "2011"} } };
|
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "wrong title", Year = "2011" } } };
|
||||||
|
|
||||||
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
||||||
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
@ -121,6 +142,27 @@ namespace PlexRequests.Services.Tests
|
||||||
Assert.That(result, Is.False);
|
Assert.That(result, Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNotAvailableTestWihtoutYear()
|
||||||
|
{
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
|
||||||
|
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "wrong title" } } };
|
||||||
|
|
||||||
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
||||||
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(searchResult);
|
||||||
|
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
var result = Checker.IsAvailable("title", null);
|
||||||
|
|
||||||
|
Assert.That(result, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void IsYearDoesNotMatchTest()
|
public void IsYearDoesNotMatchTest()
|
||||||
{
|
{
|
||||||
|
@ -141,5 +183,247 @@ namespace PlexRequests.Services.Tests
|
||||||
|
|
||||||
Assert.That(result, Is.False);
|
Assert.That(result, Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TitleDoesNotMatchTest()
|
||||||
|
{
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
|
||||||
|
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "title23", Year = "2019" } } };
|
||||||
|
|
||||||
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
||||||
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(searchResult);
|
||||||
|
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
var result = Checker.IsAvailable("title", "2019");
|
||||||
|
|
||||||
|
Assert.That(result, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TitleDoesNotMatchWithoutYearTest()
|
||||||
|
{
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
|
||||||
|
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "title23" } } };
|
||||||
|
|
||||||
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
|
||||||
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(searchResult);
|
||||||
|
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
var result = Checker.IsAvailable("title", null);
|
||||||
|
|
||||||
|
Assert.That(result, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckAndUpdateNoPlexSettingsTest()
|
||||||
|
{
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
Checker.CheckAndUpdateAll(1);
|
||||||
|
|
||||||
|
requestMock.Verify(x => x.BatchUpdate(It.IsAny<List<RequestedModel>>()), Times.Never);
|
||||||
|
requestMock.Verify(x => x.Get(It.IsAny<int>()), Times.Never);
|
||||||
|
plexMock.Verify(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckAndUpdateNoAuthSettingsTest()
|
||||||
|
{
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "123" });
|
||||||
|
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
Checker.CheckAndUpdateAll(1);
|
||||||
|
|
||||||
|
requestMock.Verify(x => x.BatchUpdate(It.IsAny<List<RequestedModel>>()), Times.Never);
|
||||||
|
requestMock.Verify(x => x.Get(It.IsAny<int>()), Times.Never);
|
||||||
|
plexMock.Verify(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckAndUpdateNoRequestsTest()
|
||||||
|
{
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "192.168.1.1" });
|
||||||
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
requestMock.Setup(x => x.GetAll()).Returns(new List<RequestedModel>());
|
||||||
|
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
Checker.CheckAndUpdateAll(1);
|
||||||
|
|
||||||
|
requestMock.Verify(x => x.BatchUpdate(It.IsAny<List<RequestedModel>>()), Times.Never);
|
||||||
|
requestMock.Verify(x => x.Get(It.IsAny<int>()), Times.Never);
|
||||||
|
plexMock.Verify(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckAndUpdateRequestsThatDoNotExistInPlexTest()
|
||||||
|
{
|
||||||
|
|
||||||
|
var requests = new List<RequestedModel> {
|
||||||
|
new RequestedModel
|
||||||
|
{
|
||||||
|
Id = 123,
|
||||||
|
Title = "title1",
|
||||||
|
Available = false,
|
||||||
|
},
|
||||||
|
new RequestedModel
|
||||||
|
{
|
||||||
|
Id=222,
|
||||||
|
Title = "title3",
|
||||||
|
Available = false
|
||||||
|
},
|
||||||
|
new RequestedModel
|
||||||
|
{
|
||||||
|
Id = 333,
|
||||||
|
Title= "missingTitle",
|
||||||
|
Available = false
|
||||||
|
},
|
||||||
|
new RequestedModel
|
||||||
|
{
|
||||||
|
Id= 444,
|
||||||
|
Title = "already found",
|
||||||
|
Available = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var search = new PlexSearch
|
||||||
|
{
|
||||||
|
Video = new List<Video>
|
||||||
|
{
|
||||||
|
new Video
|
||||||
|
{
|
||||||
|
Title = "Title4",
|
||||||
|
Year = "2012"
|
||||||
|
},
|
||||||
|
new Video
|
||||||
|
{
|
||||||
|
Title = "Title2",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Directory = new Directory1
|
||||||
|
{
|
||||||
|
Title = "Title9",
|
||||||
|
Year = "1978"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "192.168.1.1" });
|
||||||
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
requestMock.Setup(x => x.GetAll()).Returns(requests);
|
||||||
|
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(search);
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
Checker.CheckAndUpdateAll(1);
|
||||||
|
|
||||||
|
requestMock.Verify(x => x.BatchUpdate(It.IsAny<List<RequestedModel>>()), Times.Never);
|
||||||
|
requestMock.Verify(x => x.Get(It.IsAny<int>()), Times.Never);
|
||||||
|
plexMock.Verify(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>()), Times.Exactly(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckAndUpdateRequestsAllRequestsTest()
|
||||||
|
{
|
||||||
|
|
||||||
|
var requests = new List<RequestedModel> {
|
||||||
|
new RequestedModel
|
||||||
|
{
|
||||||
|
Id = 123,
|
||||||
|
Title = "title1",
|
||||||
|
Available = false,
|
||||||
|
},
|
||||||
|
new RequestedModel
|
||||||
|
{
|
||||||
|
Id=222,
|
||||||
|
Title = "title3",
|
||||||
|
Available = false
|
||||||
|
},
|
||||||
|
new RequestedModel
|
||||||
|
{
|
||||||
|
Id = 333,
|
||||||
|
Title= "missingTitle",
|
||||||
|
Available = false
|
||||||
|
},
|
||||||
|
new RequestedModel
|
||||||
|
{
|
||||||
|
Id= 444,
|
||||||
|
Title = "Hi",
|
||||||
|
Available = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var search = new PlexSearch
|
||||||
|
{
|
||||||
|
Video = new List<Video>
|
||||||
|
{
|
||||||
|
new Video
|
||||||
|
{
|
||||||
|
Title = "title1",
|
||||||
|
Year = "2012"
|
||||||
|
},
|
||||||
|
new Video
|
||||||
|
{
|
||||||
|
Title = "Title3",
|
||||||
|
}
|
||||||
|
,
|
||||||
|
new Video
|
||||||
|
{
|
||||||
|
Title = "Hi",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Directory = new Directory1
|
||||||
|
{
|
||||||
|
Title = "missingTitle",
|
||||||
|
Year = "1978"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||||
|
var requestMock = new Mock<IRequestService>();
|
||||||
|
var plexMock = new Mock<IPlexApi>();
|
||||||
|
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "192.168.1.1" });
|
||||||
|
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
|
||||||
|
requestMock.Setup(x => x.GetAll()).Returns(requests);
|
||||||
|
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(search);
|
||||||
|
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
|
||||||
|
|
||||||
|
Checker.CheckAndUpdateAll(1);
|
||||||
|
|
||||||
|
requestMock.Verify(x => x.BatchUpdate(It.IsAny<List<RequestedModel>>()), Times.Once);
|
||||||
|
requestMock.Verify(x => x.Get(It.IsAny<int>()), Times.Never);
|
||||||
|
plexMock.Verify(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>()), Times.Exactly(4));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -84,6 +84,10 @@
|
||||||
<Project>{566EFA49-68F8-4716-9693-A6B3F2624DEA}</Project>
|
<Project>{566EFA49-68F8-4716-9693-A6B3F2624DEA}</Project>
|
||||||
<Name>PlexRequests.Services</Name>
|
<Name>PlexRequests.Services</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
|
||||||
|
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
|
||||||
|
<Name>PlexRequests.Store</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Choose>
|
<Choose>
|
||||||
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
|
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
|
||||||
|
|
|
@ -48,9 +48,12 @@ namespace PlexRequests.Services
|
||||||
{
|
{
|
||||||
public AvailabilityUpdateService()
|
public AvailabilityUpdateService()
|
||||||
{
|
{
|
||||||
|
var memCache = new MemoryCacheProvider();
|
||||||
|
var dbConfig = new DbConfiguration(new SqliteFactory());
|
||||||
|
var repo = new SettingsJsonRepository(dbConfig, memCache);
|
||||||
|
|
||||||
ConfigurationReader = new ConfigurationReader();
|
ConfigurationReader = new ConfigurationReader();
|
||||||
var repo = new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider());
|
Checker = new PlexAvailabilityChecker(new SettingsServiceV2<PlexSettings>(repo), new SettingsServiceV2<AuthenticationSettings>(repo), new JsonRequestService(new RequestJsonRepository(dbConfig, memCache)), new PlexApi());
|
||||||
Checker = new PlexAvailabilityChecker(new SettingsServiceV2<PlexSettings>(repo), new SettingsServiceV2<AuthenticationSettings>(repo), new JsonRequestService(new RequestJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())), new PlexApi());
|
|
||||||
HostingEnvironment.RegisterObject(this);
|
HostingEnvironment.RegisterObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using PlexRequests.Services.Notification;
|
using PlexRequests.Services.Notification;
|
||||||
|
using PlexRequests.Core.SettingModels;
|
||||||
|
|
||||||
namespace PlexRequests.Services.Interfaces
|
namespace PlexRequests.Services.Interfaces
|
||||||
{
|
{
|
||||||
|
@ -35,5 +36,7 @@ namespace PlexRequests.Services.Interfaces
|
||||||
string NotificationName { get; }
|
string NotificationName { get; }
|
||||||
|
|
||||||
Task NotifyAsync(NotificationModel model);
|
Task NotifyAsync(NotificationModel model);
|
||||||
|
|
||||||
|
Task NotifyAsync(NotificationModel model, Settings settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,12 +27,14 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using PlexRequests.Services.Notification;
|
using PlexRequests.Services.Notification;
|
||||||
|
using PlexRequests.Core.SettingModels;
|
||||||
|
|
||||||
namespace PlexRequests.Services.Interfaces
|
namespace PlexRequests.Services.Interfaces
|
||||||
{
|
{
|
||||||
public interface INotificationService
|
public interface INotificationService
|
||||||
{
|
{
|
||||||
Task Publish(NotificationModel model);
|
Task Publish(NotificationModel model);
|
||||||
|
Task Publish(NotificationModel model, Settings settings);
|
||||||
void Subscribe(INotification notification);
|
void Subscribe(INotification notification);
|
||||||
void UnSubscribe(INotification notification);
|
void UnSubscribe(INotification notification);
|
||||||
|
|
||||||
|
|
|
@ -46,24 +46,29 @@ namespace PlexRequests.Services.Notification
|
||||||
|
|
||||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
|
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
|
||||||
private EmailNotificationSettings Settings => GetConfiguration();
|
|
||||||
public string NotificationName => "EmailMessageNotification";
|
public string NotificationName => "EmailMessageNotification";
|
||||||
|
|
||||||
public async Task NotifyAsync(NotificationModel model)
|
public async Task NotifyAsync(NotificationModel model)
|
||||||
{
|
{
|
||||||
var configuration = GetConfiguration();
|
var configuration = GetConfiguration();
|
||||||
if (!ValidateConfiguration(configuration))
|
await NotifyAsync(model, configuration);
|
||||||
{
|
}
|
||||||
return;
|
|
||||||
}
|
public async Task NotifyAsync(NotificationModel model, Settings settings)
|
||||||
|
{
|
||||||
|
if (settings == null) await NotifyAsync(model);
|
||||||
|
|
||||||
|
var emailSettings = (EmailNotificationSettings)settings;
|
||||||
|
|
||||||
|
if (!ValidateConfiguration(emailSettings)) return;
|
||||||
|
|
||||||
switch (model.NotificationType)
|
switch (model.NotificationType)
|
||||||
{
|
{
|
||||||
case NotificationType.NewRequest:
|
case NotificationType.NewRequest:
|
||||||
await EmailNewRequest(model);
|
await EmailNewRequest(model, emailSettings);
|
||||||
break;
|
break;
|
||||||
case NotificationType.Issue:
|
case NotificationType.Issue:
|
||||||
await EmailIssue(model);
|
await EmailIssue(model, emailSettings);
|
||||||
break;
|
break;
|
||||||
case NotificationType.RequestAvailable:
|
case NotificationType.RequestAvailable:
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
@ -74,6 +79,10 @@ namespace PlexRequests.Services.Notification
|
||||||
case NotificationType.AdminNote:
|
case NotificationType.AdminNote:
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
case NotificationType.Test:
|
||||||
|
await EmailTest(model, emailSettings);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
@ -100,23 +109,23 @@ namespace PlexRequests.Services.Notification
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EmailNewRequest(NotificationModel model)
|
private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings)
|
||||||
{
|
{
|
||||||
var message = new MailMessage
|
var message = new MailMessage
|
||||||
{
|
{
|
||||||
IsBodyHtml = true,
|
IsBodyHtml = true,
|
||||||
To = { new MailAddress(Settings.RecipientEmail) },
|
To = { new MailAddress(settings.RecipientEmail) },
|
||||||
Body = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}",
|
Body = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}",
|
||||||
From = new MailAddress(Settings.EmailSender),
|
From = new MailAddress(settings.EmailSender),
|
||||||
Subject = $"Plex Requests: New request for {model.Title}!"
|
Subject = $"Plex Requests: New request for {model.Title}!"
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var smtp = new SmtpClient(Settings.EmailHost, Settings.EmailPort))
|
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
|
||||||
{
|
{
|
||||||
smtp.Credentials = new NetworkCredential(Settings.EmailUsername, Settings.EmailPassword);
|
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
|
||||||
smtp.EnableSsl = Settings.Ssl;
|
smtp.EnableSsl = settings.Ssl;
|
||||||
await smtp.SendMailAsync(message).ConfigureAwait(false);
|
await smtp.SendMailAsync(message).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,23 +139,53 @@ namespace PlexRequests.Services.Notification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EmailIssue(NotificationModel model)
|
private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings)
|
||||||
{
|
{
|
||||||
var message = new MailMessage
|
var message = new MailMessage
|
||||||
{
|
{
|
||||||
IsBodyHtml = true,
|
IsBodyHtml = true,
|
||||||
To = { new MailAddress(Settings.RecipientEmail) },
|
To = { new MailAddress(settings.RecipientEmail) },
|
||||||
Body = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!",
|
Body = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!",
|
||||||
From = new MailAddress(Settings.RecipientEmail),
|
From = new MailAddress(settings.RecipientEmail),
|
||||||
Subject = $"Plex Requests: New issue for {model.Title}!"
|
Subject = $"Plex Requests: New issue for {model.Title}!"
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var smtp = new SmtpClient(Settings.EmailHost, Settings.EmailPort))
|
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
|
||||||
{
|
{
|
||||||
smtp.Credentials = new NetworkCredential(Settings.EmailUsername, Settings.EmailPassword);
|
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
|
||||||
smtp.EnableSsl = Settings.Ssl;
|
smtp.EnableSsl = settings.Ssl;
|
||||||
|
await smtp.SendMailAsync(message).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SmtpException smtp)
|
||||||
|
{
|
||||||
|
Log.Error(smtp);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings)
|
||||||
|
{
|
||||||
|
var message = new MailMessage
|
||||||
|
{
|
||||||
|
IsBodyHtml = true,
|
||||||
|
To = { new MailAddress(settings.RecipientEmail) },
|
||||||
|
Body = "This is just a test! Success!",
|
||||||
|
From = new MailAddress(settings.RecipientEmail),
|
||||||
|
Subject = "Plex Requests: Test Message!"
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
|
||||||
|
{
|
||||||
|
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
|
||||||
|
smtp.EnableSsl = settings.Ssl;
|
||||||
await smtp.SendMailAsync(message).ConfigureAwait(false);
|
await smtp.SendMailAsync(message).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
using PlexRequests.Services.Interfaces;
|
using PlexRequests.Services.Interfaces;
|
||||||
|
using PlexRequests.Core.SettingModels;
|
||||||
|
|
||||||
namespace PlexRequests.Services.Notification
|
namespace PlexRequests.Services.Notification
|
||||||
{
|
{
|
||||||
|
@ -47,6 +48,13 @@ namespace PlexRequests.Services.Notification
|
||||||
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
|
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task Publish(NotificationModel model, Settings settings)
|
||||||
|
{
|
||||||
|
var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model, settings));
|
||||||
|
|
||||||
|
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
public void Subscribe(INotification notification)
|
public void Subscribe(INotification notification)
|
||||||
{
|
{
|
||||||
Observers.TryAdd(notification.NotificationName, notification);
|
Observers.TryAdd(notification.NotificationName, notification);
|
||||||
|
@ -67,6 +75,19 @@ namespace PlexRequests.Services.Notification
|
||||||
{
|
{
|
||||||
Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception");
|
Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task NotifyAsync(INotification notification, NotificationModel model, Settings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await notification.NotifyAsync(model, settings).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -33,5 +33,6 @@ namespace PlexRequests.Services.Notification
|
||||||
RequestAvailable,
|
RequestAvailable,
|
||||||
RequestApproved,
|
RequestApproved,
|
||||||
AdminNote,
|
AdminNote,
|
||||||
|
Test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,18 +51,25 @@ namespace PlexRequests.Services.Notification
|
||||||
public string NotificationName => "PushbulletNotification";
|
public string NotificationName => "PushbulletNotification";
|
||||||
public async Task NotifyAsync(NotificationModel model)
|
public async Task NotifyAsync(NotificationModel model)
|
||||||
{
|
{
|
||||||
if (!ValidateConfiguration())
|
var configuration = GetSettings();
|
||||||
{
|
await NotifyAsync(model, configuration);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
public async Task NotifyAsync(NotificationModel model, Settings settings)
|
||||||
|
{
|
||||||
|
if (settings == null) await NotifyAsync(model);
|
||||||
|
|
||||||
|
var pushSettings = (PushbulletNotificationSettings)settings;
|
||||||
|
|
||||||
|
if (!ValidateConfiguration(pushSettings)) return;
|
||||||
|
|
||||||
switch (model.NotificationType)
|
switch (model.NotificationType)
|
||||||
{
|
{
|
||||||
case NotificationType.NewRequest:
|
case NotificationType.NewRequest:
|
||||||
await PushNewRequestAsync(model);
|
await PushNewRequestAsync(model, pushSettings);
|
||||||
break;
|
break;
|
||||||
case NotificationType.Issue:
|
case NotificationType.Issue:
|
||||||
await PushIssueAsync(model);
|
await PushIssueAsync(model, pushSettings);
|
||||||
break;
|
break;
|
||||||
case NotificationType.RequestAvailable:
|
case NotificationType.RequestAvailable:
|
||||||
break;
|
break;
|
||||||
|
@ -70,18 +77,21 @@ namespace PlexRequests.Services.Notification
|
||||||
break;
|
break;
|
||||||
case NotificationType.AdminNote:
|
case NotificationType.AdminNote:
|
||||||
break;
|
break;
|
||||||
|
case NotificationType.Test:
|
||||||
|
await PushTestAsync(model, pushSettings);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateConfiguration()
|
private bool ValidateConfiguration(PushbulletNotificationSettings settings)
|
||||||
{
|
{
|
||||||
if (!Settings.Enabled)
|
if (!settings.Enabled)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(Settings.AccessToken))
|
if (string.IsNullOrEmpty(settings.AccessToken))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -93,13 +103,13 @@ namespace PlexRequests.Services.Notification
|
||||||
return SettingsService.GetSettings();
|
return SettingsService.GetSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PushNewRequestAsync(NotificationModel model)
|
private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings)
|
||||||
{
|
{
|
||||||
var message = $"{model.Title} has been requested by user: {model.User}";
|
var message = $"{model.Title} has been requested by user: {model.User}";
|
||||||
var pushTitle = $"Plex Requests: {model.Title} has been requested!";
|
var pushTitle = $"Plex Requests: {model.Title} has been requested!";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await PushbulletApi.PushAsync(Settings.AccessToken, pushTitle, message, Settings.DeviceIdentifier);
|
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
||||||
|
@ -111,13 +121,31 @@ namespace PlexRequests.Services.Notification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PushIssueAsync(NotificationModel model)
|
private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings)
|
||||||
{
|
{
|
||||||
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
||||||
var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}";
|
var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await PushbulletApi.PushAsync(Settings.AccessToken, pushTitle, message, Settings.DeviceIdentifier);
|
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PushTestAsync(NotificationModel model, PushbulletNotificationSettings settings)
|
||||||
|
{
|
||||||
|
var message = "This is just a test! Success!";
|
||||||
|
var pushTitle = "Plex Requests: Test Message!";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
||||||
|
|
|
@ -51,18 +51,25 @@ namespace PlexRequests.Services.Notification
|
||||||
public string NotificationName => "PushoverNotification";
|
public string NotificationName => "PushoverNotification";
|
||||||
public async Task NotifyAsync(NotificationModel model)
|
public async Task NotifyAsync(NotificationModel model)
|
||||||
{
|
{
|
||||||
if (!ValidateConfiguration())
|
var configuration = GetSettings();
|
||||||
{
|
await NotifyAsync(model, configuration);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
public async Task NotifyAsync(NotificationModel model, Settings settings)
|
||||||
|
{
|
||||||
|
if (settings == null) await NotifyAsync(model);
|
||||||
|
|
||||||
|
var pushSettings = (PushoverNotificationSettings)settings;
|
||||||
|
|
||||||
|
if (!ValidateConfiguration(pushSettings)) return;
|
||||||
|
|
||||||
switch (model.NotificationType)
|
switch (model.NotificationType)
|
||||||
{
|
{
|
||||||
case NotificationType.NewRequest:
|
case NotificationType.NewRequest:
|
||||||
await PushNewRequestAsync(model);
|
await PushNewRequestAsync(model, pushSettings);
|
||||||
break;
|
break;
|
||||||
case NotificationType.Issue:
|
case NotificationType.Issue:
|
||||||
await PushIssueAsync(model);
|
await PushIssueAsync(model, pushSettings);
|
||||||
break;
|
break;
|
||||||
case NotificationType.RequestAvailable:
|
case NotificationType.RequestAvailable:
|
||||||
break;
|
break;
|
||||||
|
@ -70,18 +77,21 @@ namespace PlexRequests.Services.Notification
|
||||||
break;
|
break;
|
||||||
case NotificationType.AdminNote:
|
case NotificationType.AdminNote:
|
||||||
break;
|
break;
|
||||||
|
case NotificationType.Test:
|
||||||
|
await PushTestAsync(model, pushSettings);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateConfiguration()
|
private bool ValidateConfiguration(PushoverNotificationSettings settings)
|
||||||
{
|
{
|
||||||
if (!Settings.Enabled)
|
if (!settings.Enabled)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(Settings.AccessToken) || string.IsNullOrEmpty(Settings.UserToken))
|
if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -93,12 +103,12 @@ namespace PlexRequests.Services.Notification
|
||||||
return SettingsService.GetSettings();
|
return SettingsService.GetSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PushNewRequestAsync(NotificationModel model)
|
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: {model.Title} has been requested by user: {model.User}";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await PushoverApi.PushAsync(Settings.AccessToken, message, Settings.UserToken);
|
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
|
||||||
if (result?.status != 1)
|
if (result?.status != 1)
|
||||||
{
|
{
|
||||||
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
|
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
|
||||||
|
@ -110,12 +120,29 @@ namespace PlexRequests.Services.Notification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PushIssueAsync(NotificationModel model)
|
private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings)
|
||||||
{
|
{
|
||||||
var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await PushoverApi.PushAsync(Settings.AccessToken, message, Settings.UserToken);
|
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
|
||||||
|
if (result?.status != 1)
|
||||||
|
{
|
||||||
|
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings)
|
||||||
|
{
|
||||||
|
var message = $"Plex Requests: Test Message!";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
|
||||||
if (result?.status != 1)
|
if (result?.status != 1)
|
||||||
{
|
{
|
||||||
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
|
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
|
||||||
|
|
|
@ -24,14 +24,17 @@
|
||||||
// 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 System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
using PlexRequests.Api.Interfaces;
|
using PlexRequests.Api.Interfaces;
|
||||||
|
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.Exceptions;
|
using PlexRequests.Helpers.Exceptions;
|
||||||
using PlexRequests.Services.Interfaces;
|
using PlexRequests.Services.Interfaces;
|
||||||
using PlexRequests.Store;
|
using PlexRequests.Store;
|
||||||
|
@ -52,33 +55,77 @@ namespace PlexRequests.Services
|
||||||
private ISettingsService<AuthenticationSettings> Auth { get; }
|
private ISettingsService<AuthenticationSettings> Auth { get; }
|
||||||
private IRequestService RequestService { get; }
|
private IRequestService RequestService { get; }
|
||||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
private IPlexApi PlexApi { get; set; }
|
private IPlexApi PlexApi { get; }
|
||||||
|
|
||||||
|
|
||||||
public void CheckAndUpdateAll(long check)
|
public void CheckAndUpdateAll(long check)
|
||||||
{
|
{
|
||||||
|
Log.Trace("This is check no. {0}", check);
|
||||||
|
Log.Trace("Getting the settings");
|
||||||
var plexSettings = Plex.GetSettings();
|
var plexSettings = Plex.GetSettings();
|
||||||
var authSettings = Auth.GetSettings();
|
var authSettings = Auth.GetSettings();
|
||||||
|
Log.Trace("Getting all the requests");
|
||||||
var requests = RequestService.GetAll();
|
var requests = RequestService.GetAll();
|
||||||
|
|
||||||
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
|
var requestedModels = requests as RequestedModel[] ?? requests.Where(x => !x.Available).ToArray();
|
||||||
if (!ValidateSettings(plexSettings, authSettings, requestedModels))
|
Log.Trace("Requests Count {0}", requestedModels.Length);
|
||||||
|
|
||||||
|
if (!ValidateSettings(plexSettings, authSettings) || !requestedModels.Any())
|
||||||
{
|
{
|
||||||
|
Log.Info("Validation of the settings failed or there is no requests.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modifiedModel = new List<RequestedModel>();
|
var modifiedModel = new List<RequestedModel>();
|
||||||
foreach (var r in requestedModels)
|
foreach (var r in requestedModels)
|
||||||
{
|
{
|
||||||
var results = PlexApi.SearchContent(authSettings.PlexAuthToken, r.Title, plexSettings.FullUri);
|
Log.Trace("We are going to see if Plex has the following title: {0}", r.Title);
|
||||||
var result = results.Video.FirstOrDefault(x => x.Title == r.Title);
|
PlexSearch results;
|
||||||
var originalRequest = RequestService.Get(r.Id);
|
try
|
||||||
|
{
|
||||||
|
results = PlexApi.SearchContent(authSettings.PlexAuthToken, r.Title, plexSettings.FullUri);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("We failed to search Plex for the following request:");
|
||||||
|
Log.Error(r.DumpJson());
|
||||||
|
Log.Error(e);
|
||||||
|
break; // Let's finish processing and not crash the process, there is a reason why we cannot connect.
|
||||||
|
}
|
||||||
|
|
||||||
originalRequest.Available = result != null;
|
if (results == null)
|
||||||
modifiedModel.Add(originalRequest);
|
{
|
||||||
|
Log.Trace("Could not find any matching result for this title.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Trace("Search results from Plex for the following request: {0}", r.Title);
|
||||||
|
Log.Trace(results.DumpJson());
|
||||||
|
|
||||||
|
var videoResult = results.Video.FirstOrDefault(x => x.Title == r.Title);
|
||||||
|
var directoryResult = results.Directory?.Title.Equals(r.Title, StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
|
||||||
|
Log.Trace("The result from Plex where the title matches for the video : {0}", videoResult != null);
|
||||||
|
Log.Trace("The result from Plex where the title matches for the directory : {0}", directoryResult != null);
|
||||||
|
|
||||||
|
var directoryResultVal = directoryResult ?? false;
|
||||||
|
|
||||||
|
if (videoResult != null || directoryResultVal)
|
||||||
|
{
|
||||||
|
r.Available = true;
|
||||||
|
modifiedModel.Add(r);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Trace("The result from Plex where the title's match was null, so that means the content is not yet in Plex.");
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestService.BatchUpdate(modifiedModel);
|
Log.Trace("Updating the requests now");
|
||||||
|
Log.Trace("Requests that will be updates:");
|
||||||
|
Log.Trace(modifiedModel.SelectMany(x => x.Title).DumpJson());
|
||||||
|
|
||||||
|
if(modifiedModel.Any())
|
||||||
|
{ RequestService.BatchUpdate(modifiedModel);}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -90,45 +137,31 @@ namespace PlexRequests.Services
|
||||||
/// <exception cref="ApplicationSettingsException">The settings are not configured for Plex or Authentication</exception>
|
/// <exception cref="ApplicationSettingsException">The settings are not configured for Plex or Authentication</exception>
|
||||||
public bool IsAvailable(string title, string year)
|
public bool IsAvailable(string title, string year)
|
||||||
{
|
{
|
||||||
|
Log.Trace("Checking if the following {0} {1} is available in Plex", title, year);
|
||||||
var plexSettings = Plex.GetSettings();
|
var plexSettings = Plex.GetSettings();
|
||||||
var authSettings = Auth.GetSettings();
|
var authSettings = Auth.GetSettings();
|
||||||
|
|
||||||
if (!ValidateSettings(plexSettings, authSettings))
|
if (!ValidateSettings(plexSettings, authSettings))
|
||||||
{
|
{
|
||||||
|
Log.Warn("The settings are not configured");
|
||||||
throw new ApplicationSettingsException("The settings are not configured for Plex or Authentication");
|
throw new ApplicationSettingsException("The settings are not configured for Plex or Authentication");
|
||||||
}
|
}
|
||||||
|
var results = PlexApi.SearchContent(authSettings.PlexAuthToken, title, plexSettings.FullUri);
|
||||||
if (!string.IsNullOrEmpty(year))
|
if (!string.IsNullOrEmpty(year))
|
||||||
{
|
{
|
||||||
var results = PlexApi.SearchContent(authSettings.PlexAuthToken, title, plexSettings.FullUri);
|
var result = results.Video?.FirstOrDefault(x => x.Title.Equals(title, StringComparison.InvariantCultureIgnoreCase) && x.Year == year);
|
||||||
var result = results.Video?.FirstOrDefault(x => x.Title.Contains(title) && x.Year == year);
|
var directoryTitle = string.Equals(results.Directory?.Title, title, StringComparison.CurrentCultureIgnoreCase) && results.Directory?.Year == year;
|
||||||
var directoryTitle = results.Directory?.Title == title && results.Directory?.Year == year;
|
|
||||||
return result?.Title != null || directoryTitle;
|
return result?.Title != null || directoryTitle;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var results = PlexApi.SearchContent(authSettings.PlexAuthToken, title, plexSettings.FullUri);
|
var result = results.Video?.FirstOrDefault(x => x.Title.Equals(title, StringComparison.InvariantCultureIgnoreCase));
|
||||||
var result = results.Video?.FirstOrDefault(x => x.Title.Contains(title));
|
var directoryTitle = string.Equals(results.Directory?.Title, title, StringComparison.CurrentCultureIgnoreCase);
|
||||||
var directoryTitle = results.Directory?.Title == title;
|
|
||||||
return result?.Title != null || directoryTitle;
|
return result?.Title != null || directoryTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth, IEnumerable<RequestedModel> requests)
|
|
||||||
{
|
|
||||||
if (plex.Ip == null || auth.PlexAuthToken == null || requests == null)
|
|
||||||
{
|
|
||||||
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!requests.Any())
|
|
||||||
{
|
|
||||||
Log.Info("We have no requests to check if they are available on Plex.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth)
|
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth)
|
||||||
{
|
{
|
||||||
if (plex?.Ip == null || auth?.PlexAuthToken == null)
|
if (plex?.Ip == null || auth?.PlexAuthToken == null)
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace PlexRequests.Services
|
||||||
{
|
{
|
||||||
public class UpdateInterval : IIntervals
|
public class UpdateInterval : IIntervals
|
||||||
{
|
{
|
||||||
public TimeSpan Notification => TimeSpan.FromMinutes(5);
|
public TimeSpan Notification => TimeSpan.FromMinutes(10);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,12 +27,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using Mono.Data.Sqlite;
|
using Mono.Data.Sqlite;
|
||||||
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using PlexRequests.Helpers;
|
|
||||||
using PlexRequests.Store.Repository;
|
|
||||||
|
|
||||||
namespace PlexRequests.Store
|
namespace PlexRequests.Store
|
||||||
{
|
{
|
||||||
|
@ -44,12 +43,14 @@ namespace PlexRequests.Store
|
||||||
Factory = provider;
|
Factory = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqliteFactory Factory { get; set; }
|
private SqliteFactory Factory { get; }
|
||||||
|
private string CurrentPath =>Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, DbFile);
|
||||||
|
|
||||||
public virtual bool CheckDb()
|
public virtual bool CheckDb()
|
||||||
{
|
{
|
||||||
Log.Trace("Checking DB");
|
Log.Trace("Checking DB");
|
||||||
if (!File.Exists(DbFile))
|
Console.WriteLine("Location of the database: {0}",CurrentPath);
|
||||||
|
if (!File.Exists(CurrentPath))
|
||||||
{
|
{
|
||||||
Log.Trace("DB doesn't exist, creating a new one");
|
Log.Trace("DB doesn't exist, creating a new one");
|
||||||
CreateDatabase();
|
CreateDatabase();
|
||||||
|
@ -59,7 +60,7 @@ namespace PlexRequests.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DbFile = "PlexRequests.sqlite";
|
public string DbFile = "PlexRequests.sqlite";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the database connection.
|
/// Gets the database connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -72,7 +73,7 @@ namespace PlexRequests.Store
|
||||||
{
|
{
|
||||||
throw new SqliteException("Factory returned null");
|
throw new SqliteException("Factory returned null");
|
||||||
}
|
}
|
||||||
fact.ConnectionString = "Data Source=" + DbFile;
|
fact.ConnectionString = "Data Source=" + CurrentPath;
|
||||||
return fact;
|
return fact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,14 +84,16 @@ namespace PlexRequests.Store
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (File.Create(DbFile))
|
using (File.Create(CurrentPath))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine(e.Message);
|
Log.Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,5 +34,6 @@ namespace PlexRequests.Store.Models
|
||||||
public int ProviderId { get; set; }
|
public int ProviderId { get; set; }
|
||||||
public byte[] Content { get; set; }
|
public byte[] Content { get; set; }
|
||||||
public RequestType Type { get; set; }
|
public RequestType Type { get; set; }
|
||||||
|
public string MusicId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -52,6 +52,7 @@
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
|
|
@ -37,13 +37,11 @@ namespace PlexRequests.Store.Repository
|
||||||
public class RequestJsonRepository : IRequestRepository
|
public class RequestJsonRepository : IRequestRepository
|
||||||
{
|
{
|
||||||
private ICacheProvider Cache { get; }
|
private ICacheProvider Cache { get; }
|
||||||
|
|
||||||
private string TypeName { get; }
|
|
||||||
public RequestJsonRepository(ISqliteConfiguration config, ICacheProvider cacheProvider)
|
public RequestJsonRepository(ISqliteConfiguration config, ICacheProvider cacheProvider)
|
||||||
{
|
{
|
||||||
Db = config;
|
Db = config;
|
||||||
Cache = cacheProvider;
|
Cache = cacheProvider;
|
||||||
TypeName = typeof(RequestJsonRepository).Name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ISqliteConfiguration Db { get; }
|
private ISqliteConfiguration Db { get; }
|
||||||
|
@ -60,7 +58,7 @@ namespace PlexRequests.Store.Repository
|
||||||
|
|
||||||
public IEnumerable<RequestBlobs> GetAll()
|
public IEnumerable<RequestBlobs> GetAll()
|
||||||
{
|
{
|
||||||
var key = TypeName + "GetAll";
|
var key = "GetAll";
|
||||||
var item = Cache.GetOrSet(key, () =>
|
var item = Cache.GetOrSet(key, () =>
|
||||||
{
|
{
|
||||||
using (var con = Db.DbConnection())
|
using (var con = Db.DbConnection())
|
||||||
|
@ -74,7 +72,7 @@ namespace PlexRequests.Store.Repository
|
||||||
|
|
||||||
public RequestBlobs Get(int id)
|
public RequestBlobs Get(int id)
|
||||||
{
|
{
|
||||||
var key = TypeName + "Get" + id;
|
var key = "Get" + id;
|
||||||
var item = Cache.GetOrSet(key, () =>
|
var item = Cache.GetOrSet(key, () =>
|
||||||
{
|
{
|
||||||
using (var con = Db.DbConnection())
|
using (var con = Db.DbConnection())
|
||||||
|
@ -107,7 +105,7 @@ namespace PlexRequests.Store.Repository
|
||||||
private void ResetCache()
|
private void ResetCache()
|
||||||
{
|
{
|
||||||
Cache.Remove("Get");
|
Cache.Remove("Get");
|
||||||
Cache.Remove(TypeName + "GetAll");
|
Cache.Remove("GetAll");
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UpdateAll(IEnumerable<RequestBlobs> entity)
|
public bool UpdateAll(IEnumerable<RequestBlobs> entity)
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
using Dapper.Contrib.Extensions;
|
using Dapper.Contrib.Extensions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace PlexRequests.Store
|
namespace PlexRequests.Store
|
||||||
{
|
{
|
||||||
[Table("Requested")]
|
[Table("Requested")]
|
||||||
public class RequestedModel : Entity
|
public class RequestedModel : Entity
|
||||||
{
|
{
|
||||||
|
public RequestedModel()
|
||||||
|
{
|
||||||
|
RequestedUsers = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
// ReSharper disable once IdentifierTypo
|
// ReSharper disable once IdentifierTypo
|
||||||
public int ProviderId { get; set; }
|
public int ProviderId { get; set; }
|
||||||
public string ImdbId { get; set; }
|
public string ImdbId { get; set; }
|
||||||
|
@ -18,7 +24,10 @@ namespace PlexRequests.Store
|
||||||
public RequestType Type { get; set; }
|
public RequestType Type { get; set; }
|
||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public bool Approved { get; set; }
|
public bool Approved { get; set; }
|
||||||
|
|
||||||
|
[Obsolete("Use RequestedUsers")]
|
||||||
public string RequestedBy { get; set; }
|
public string RequestedBy { get; set; }
|
||||||
|
|
||||||
public DateTime RequestedDate { get; set; }
|
public DateTime RequestedDate { get; set; }
|
||||||
public bool Available { get; set; }
|
public bool Available { get; set; }
|
||||||
public IssueState Issues { get; set; }
|
public IssueState Issues { get; set; }
|
||||||
|
@ -27,12 +36,48 @@ namespace PlexRequests.Store
|
||||||
public int[] SeasonList { get; set; }
|
public int[] SeasonList { get; set; }
|
||||||
public int SeasonCount { get; set; }
|
public int SeasonCount { get; set; }
|
||||||
public string SeasonsRequested { get; set; }
|
public string SeasonsRequested { get; set; }
|
||||||
|
public string MusicBrainzId { get; set; }
|
||||||
|
public List<string> RequestedUsers { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<string> AllUsers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var u = new List<string>();
|
||||||
|
if (!string.IsNullOrEmpty(RequestedBy))
|
||||||
|
{
|
||||||
|
u.Add(RequestedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RequestedUsers.Any())
|
||||||
|
{
|
||||||
|
u.AddRange(RequestedUsers.Where(requestedUser => requestedUser != RequestedBy));
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool CanApprove
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return !Approved && !Available;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UserHasRequested(string username)
|
||||||
|
{
|
||||||
|
return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RequestType
|
public enum RequestType
|
||||||
{
|
{
|
||||||
Movie,
|
Movie,
|
||||||
TvShow
|
TvShow,
|
||||||
|
Album
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum IssueState
|
public enum IssueState
|
||||||
|
|
|
@ -25,7 +25,8 @@ CREATE TABLE IF NOT EXISTS RequestBlobs
|
||||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
ProviderId INTEGER NOT NULL,
|
ProviderId INTEGER NOT NULL,
|
||||||
Type INTEGER NOT NULL,
|
Type INTEGER NOT NULL,
|
||||||
Content BLOB NOT NULL
|
Content BLOB NOT NULL,
|
||||||
|
MusicId TEXT
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS RequestBlobs_Id ON RequestBlobs (Id);
|
CREATE UNIQUE INDEX IF NOT EXISTS RequestBlobs_Id ON RequestBlobs (Id);
|
||||||
|
|
||||||
|
@ -40,3 +41,9 @@ CREATE TABLE IF NOT EXISTS Logs
|
||||||
Exception varchar(100) NOT NULL
|
Exception varchar(100) NOT NULL
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS Logs_Id ON Logs (Id);
|
CREATE UNIQUE INDEX IF NOT EXISTS Logs_Id ON Logs (Id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS DBInfo
|
||||||
|
(
|
||||||
|
SchemaVersion INTEGER
|
||||||
|
|
||||||
|
);
|
|
@ -25,7 +25,7 @@
|
||||||
// ***********************************************************************
|
// ***********************************************************************
|
||||||
#endregion
|
#endregion
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using Dapper.Contrib.Extensions;
|
using Dapper.Contrib.Extensions;
|
||||||
|
|
||||||
|
@ -44,6 +44,57 @@ namespace PlexRequests.Store
|
||||||
connection.Close();
|
connection.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AlterTable(IDbConnection connection, string tableName, string alterType, string newColumn, bool isNullable, string dataType)
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var result = connection.Query<TableInfo>($"PRAGMA table_info({tableName});");
|
||||||
|
if (result.Any(x => x.name == newColumn))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = $"ALTER TABLE {tableName} {alterType} {newColumn} {dataType}";
|
||||||
|
if (isNullable)
|
||||||
|
{
|
||||||
|
query = query + " NOT NULL";
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.Execute(query);
|
||||||
|
|
||||||
|
connection.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DbInfo GetSchemaVersion(this IDbConnection con)
|
||||||
|
{
|
||||||
|
con.Open();
|
||||||
|
var result = con.Query<DbInfo>("SELECT * FROM DBInfo");
|
||||||
|
con.Close();
|
||||||
|
|
||||||
|
return result.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateSchemaVersion(this IDbConnection con, int version)
|
||||||
|
{
|
||||||
|
con.Open();
|
||||||
|
con.Query($"UPDATE DBInfo SET SchemaVersion = {version}");
|
||||||
|
con.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateSchema(this IDbConnection con)
|
||||||
|
{
|
||||||
|
con.Open();
|
||||||
|
con.Query("INSERT INTO DBInfo (SchemaVersion) values (0)");
|
||||||
|
con.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[Table("DBInfo")]
|
||||||
|
public class DbInfo
|
||||||
|
{
|
||||||
|
public int SchemaVersion { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[Table("sqlite_master")]
|
[Table("sqlite_master")]
|
||||||
public class SqliteMasterTable
|
public class SqliteMasterTable
|
||||||
{
|
{
|
||||||
|
@ -54,5 +105,17 @@ namespace PlexRequests.Store
|
||||||
public long rootpage { get; set; }
|
public long rootpage { get; set; }
|
||||||
public string sql { get; set; }
|
public string sql { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Table("table_info")]
|
||||||
|
public class TableInfo
|
||||||
|
{
|
||||||
|
public int cid { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public int notnull { get; set; }
|
||||||
|
public string dflt_value { get; set; }
|
||||||
|
public int pk { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ namespace PlexRequests.UI.Tests
|
||||||
private Mock<ISettingsService<EmailNotificationSettings>> EmailMock { get; set; }
|
private Mock<ISettingsService<EmailNotificationSettings>> EmailMock { get; set; }
|
||||||
private Mock<ISettingsService<PushbulletNotificationSettings>> PushbulletSettings { get; set; }
|
private Mock<ISettingsService<PushbulletNotificationSettings>> PushbulletSettings { get; set; }
|
||||||
private Mock<ISettingsService<PushoverNotificationSettings>> PushoverSettings { get; set; }
|
private Mock<ISettingsService<PushoverNotificationSettings>> PushoverSettings { get; set; }
|
||||||
|
private Mock<ISettingsService<HeadphonesSettings>> HeadphonesSettings { get; set; }
|
||||||
private Mock<IPlexApi> PlexMock { get; set; }
|
private Mock<IPlexApi> PlexMock { get; set; }
|
||||||
private Mock<ISonarrApi> SonarrApiMock { get; set; }
|
private Mock<ISonarrApi> SonarrApiMock { get; set; }
|
||||||
private Mock<IPushbulletApi> PushbulletApi { get; set; }
|
private Mock<IPushbulletApi> PushbulletApi { get; set; }
|
||||||
|
@ -94,6 +95,7 @@ namespace PlexRequests.UI.Tests
|
||||||
PushoverSettings = new Mock<ISettingsService<PushoverNotificationSettings>>();
|
PushoverSettings = new Mock<ISettingsService<PushoverNotificationSettings>>();
|
||||||
PushoverApi = new Mock<IPushoverApi>();
|
PushoverApi = new Mock<IPushoverApi>();
|
||||||
NotificationService = new Mock<INotificationService>();
|
NotificationService = new Mock<INotificationService>();
|
||||||
|
HeadphonesSettings = new Mock<ISettingsService<HeadphonesSettings>>();
|
||||||
|
|
||||||
Bootstrapper = new ConfigurableBootstrapper(with =>
|
Bootstrapper = new ConfigurableBootstrapper(with =>
|
||||||
{
|
{
|
||||||
|
@ -114,6 +116,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(HeadphonesSettings.Object);
|
||||||
with.RootPathProvider<TestRootPathProvider>();
|
with.RootPathProvider<TestRootPathProvider>();
|
||||||
with.RequestStartup((container, pipelines, context) =>
|
with.RequestStartup((container, pipelines, context) =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -76,9 +76,9 @@ namespace PlexRequests.UI
|
||||||
container.Register<ISettingsService<EmailNotificationSettings>, SettingsServiceV2<EmailNotificationSettings>>();
|
container.Register<ISettingsService<EmailNotificationSettings>, SettingsServiceV2<EmailNotificationSettings>>();
|
||||||
container.Register<ISettingsService<PushbulletNotificationSettings>, SettingsServiceV2<PushbulletNotificationSettings>>();
|
container.Register<ISettingsService<PushbulletNotificationSettings>, SettingsServiceV2<PushbulletNotificationSettings>>();
|
||||||
container.Register<ISettingsService<PushoverNotificationSettings>, SettingsServiceV2<PushoverNotificationSettings>>();
|
container.Register<ISettingsService<PushoverNotificationSettings>, SettingsServiceV2<PushoverNotificationSettings>>();
|
||||||
|
container.Register<ISettingsService<HeadphonesSettings>, SettingsServiceV2<HeadphonesSettings>>();
|
||||||
|
|
||||||
// Repo's
|
// Repo's
|
||||||
container.Register<IRepository<RequestedModel>, GenericRepository<RequestedModel>>();
|
|
||||||
container.Register<IRepository<LogEntity>, GenericRepository<LogEntity>>();
|
container.Register<IRepository<LogEntity>, GenericRepository<LogEntity>>();
|
||||||
container.Register<IRequestService, JsonRequestService>();
|
container.Register<IRequestService, JsonRequestService>();
|
||||||
container.Register<ISettingsRepository, SettingsJsonRepository>();
|
container.Register<ISettingsRepository, SettingsJsonRepository>();
|
||||||
|
@ -95,19 +95,21 @@ namespace PlexRequests.UI
|
||||||
container.Register<ISickRageApi, SickrageApi>();
|
container.Register<ISickRageApi, SickrageApi>();
|
||||||
container.Register<ISonarrApi, SonarrApi>();
|
container.Register<ISonarrApi, SonarrApi>();
|
||||||
container.Register<IPlexApi, PlexApi>();
|
container.Register<IPlexApi, PlexApi>();
|
||||||
|
container.Register<IMusicBrainzApi, MusicBrainzApi>();
|
||||||
|
container.Register<IHeadphonesApi, HeadphonesApi>();
|
||||||
|
|
||||||
// NotificationService
|
// NotificationService
|
||||||
container.Register<INotificationService, NotificationService>().AsSingleton();
|
container.Register<INotificationService, NotificationService>().AsSingleton();
|
||||||
|
|
||||||
SubscribeAllObservers(container);
|
SubscribeAllObservers(container);
|
||||||
base.ConfigureRequestContainer(container, context);
|
base.ConfigureRequestContainer(container, context);
|
||||||
|
|
||||||
|
TaskManager.TaskFactory = new PlexTaskFactory();
|
||||||
|
TaskManager.Initialize(new PlexRegistry());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
|
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
|
||||||
{
|
{
|
||||||
TaskManager.TaskFactory = new PlexTaskFactory();
|
|
||||||
TaskManager.Initialize(new PlexRegistry());
|
|
||||||
|
|
||||||
CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default);
|
CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default);
|
||||||
|
|
||||||
StaticConfiguration.DisableErrorTraces = false;
|
StaticConfiguration.DisableErrorTraces = false;
|
||||||
|
@ -123,11 +125,12 @@ namespace PlexRequests.UI
|
||||||
|
|
||||||
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
|
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
|
||||||
|
|
||||||
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
|
||||||
ServicePointManager.ServerCertificateValidationCallback +=
|
ServicePointManager.ServerCertificateValidationCallback +=
|
||||||
(sender, certificate, chain, sslPolicyErrors) => true;
|
(sender, certificate, chain, sslPolicyErrors) => true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" };
|
protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" };
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,9 @@
|
||||||
|
|
||||||
.form-control-custom {
|
.form-control-custom {
|
||||||
background-color: #4e5d6c !important;
|
background-color: #4e5d6c !important;
|
||||||
color: white !important; }
|
color: white !important;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: 0 0 0 !important; }
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 3.5rem !important;
|
font-size: 3.5rem !important;
|
||||||
|
@ -40,6 +42,22 @@ label {
|
||||||
margin-bottom: 0.5rem !important;
|
margin-bottom: 0.5rem !important;
|
||||||
font-size: 16px !important; }
|
font-size: 16px !important; }
|
||||||
|
|
||||||
|
.nav-tabs > li.active > a,
|
||||||
|
.nav-tabs > li.active > a:hover,
|
||||||
|
.nav-tabs > li.active > a:focus {
|
||||||
|
background: #4e5d6c; }
|
||||||
|
|
||||||
|
.navbar .nav a .fa,
|
||||||
|
.dropdown-menu a .fa {
|
||||||
|
font-size: 130%;
|
||||||
|
top: 1px;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px; }
|
||||||
|
|
||||||
|
.dropdown-menu a .fa {
|
||||||
|
top: 2px; }
|
||||||
|
|
||||||
.btn-danger-outline {
|
.btn-danger-outline {
|
||||||
color: #d9534f !important;
|
color: #d9534f !important;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -126,3 +144,68 @@ label {
|
||||||
#tvList .mix {
|
#tvList .mix {
|
||||||
display: none; }
|
display: none; }
|
||||||
|
|
||||||
|
.scroll-top-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 99999999;
|
||||||
|
background-color: #4e5d6c;
|
||||||
|
color: #eeeeee;
|
||||||
|
width: 50px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
right: 30px;
|
||||||
|
bottom: 30px;
|
||||||
|
padding-top: 2px;
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
border-bottom-right-radius: 10px;
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
|
-webkit-transition: all 0.5s ease-in-out;
|
||||||
|
-moz-transition: all 0.5s ease-in-out;
|
||||||
|
-ms-transition: all 0.5s ease-in-out;
|
||||||
|
-o-transition: all 0.5s ease-in-out;
|
||||||
|
transition: all 0.5s ease-in-out; }
|
||||||
|
|
||||||
|
.scroll-top-wrapper:hover {
|
||||||
|
background-color: #637689; }
|
||||||
|
|
||||||
|
.scroll-top-wrapper.show {
|
||||||
|
visibility: visible;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1.0; }
|
||||||
|
|
||||||
|
.scroll-top-wrapper i.fa {
|
||||||
|
line-height: inherit; }
|
||||||
|
|
||||||
|
.no-search-results {
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
|
.no-search-results .no-search-results-icon {
|
||||||
|
font-size: 10em;
|
||||||
|
color: #4e5d6c; }
|
||||||
|
|
||||||
|
.no-search-results .no-search-results-text {
|
||||||
|
margin: 20px 0;
|
||||||
|
color: #ccc; }
|
||||||
|
|
||||||
|
.form-control-search {
|
||||||
|
padding: 25px 105px 25px 16px; }
|
||||||
|
|
||||||
|
.form-control-withbuttons {
|
||||||
|
padding-right: 105px; }
|
||||||
|
|
||||||
|
.input-group-addon .btn-group {
|
||||||
|
position: absolute;
|
||||||
|
right: 45px;
|
||||||
|
z-index: 3;
|
||||||
|
top: 13px;
|
||||||
|
box-shadow: 0 0 0; }
|
||||||
|
|
||||||
|
.input-group-addon .btn-group .btn {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.7) !important;
|
||||||
|
padding: 3px 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.7) !important; }
|
||||||
|
|
||||||
|
|
2
PlexRequests.UI/Content/custom.min.css
vendored
2
PlexRequests.UI/Content/custom.min.css
vendored
|
@ -1 +1 @@
|
||||||
@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}
|
@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;border-radius:0;box-shadow:0 0 0 !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.navbar .nav a .fa,.dropdown-menu a .fa{font-size:130%;top:1px;position:relative;display:inline-block;margin-right:5px;}.dropdown-menu a .fa{top:2px;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}.no-search-results{text-align:center;}.no-search-results .no-search-results-icon{font-size:10em;color:#4e5d6c;}.no-search-results .no-search-results-text{margin:20px 0;color:#ccc;}.form-control-search{padding:25px 105px 25px 16px;}.form-control-withbuttons{padding-right:105px;}.input-group-addon .btn-group{position:absolute;right:45px;z-index:3;top:13px;box-shadow:0 0 0;}.input-group-addon .btn-group .btn{border:1px solid rgba(255,255,255,.7) !important;padding:3px 12px;color:rgba(255,255,255,.7) !important;}
|
|
@ -1,11 +1,14 @@
|
||||||
$form-color: #4e5d6c;
|
$form-color: #4e5d6c;
|
||||||
|
$form-color-lighter: #637689;
|
||||||
$primary-colour: #df691a;
|
$primary-colour: #df691a;
|
||||||
$primary-colour-outline: #ff761b;
|
$primary-colour-outline: #ff761b;
|
||||||
$info-colour: #5bc0de;
|
$info-colour: #5bc0de;
|
||||||
$warning-colour: #f0ad4e;
|
$warning-colour: #f0ad4e;
|
||||||
$danger-colour: #d9534f;
|
$danger-colour: #d9534f;
|
||||||
$success-colour: #5cb85c;
|
$success-colour: #5cb85c;
|
||||||
$i:!important;
|
$i:
|
||||||
|
!important
|
||||||
|
;
|
||||||
|
|
||||||
@media (min-width: 768px ) {
|
@media (min-width: 768px ) {
|
||||||
.row {
|
.row {
|
||||||
|
@ -42,6 +45,8 @@ $i:!important;
|
||||||
.form-control-custom {
|
.form-control-custom {
|
||||||
background-color: $form-color $i;
|
background-color: $form-color $i;
|
||||||
color: white $i;
|
color: white $i;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: 0 0 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +70,25 @@ label {
|
||||||
font-size: 16px $i;
|
font-size: 16px $i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-tabs > li.active > a,
|
||||||
|
.nav-tabs > li.active > a:hover,
|
||||||
|
.nav-tabs > li.active > a:focus {
|
||||||
|
background: #4e5d6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .nav a .fa,
|
||||||
|
.dropdown-menu a .fa {
|
||||||
|
font-size: 130%;
|
||||||
|
top: 1px;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu a .fa {
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-danger-outline {
|
.btn-danger-outline {
|
||||||
color: $danger-colour $i;
|
color: $danger-colour $i;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -156,9 +180,89 @@ label {
|
||||||
border-color: $success-colour $i;
|
border-color: $success-colour $i;
|
||||||
}
|
}
|
||||||
|
|
||||||
#movieList .mix{
|
#movieList .mix {
|
||||||
display: none;
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tvList .mix {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
$border-radius: 10px;
|
||||||
|
|
||||||
|
.scroll-top-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 99999999;
|
||||||
|
background-color: $form-color;
|
||||||
|
color: #eeeeee;
|
||||||
|
width: 50px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
right: 30px;
|
||||||
|
bottom: 30px;
|
||||||
|
padding-top: 2px;
|
||||||
|
border-top-left-radius: $border-radius;
|
||||||
|
border-top-right-radius: $border-radius;
|
||||||
|
border-bottom-right-radius: $border-radius;
|
||||||
|
border-bottom-left-radius: $border-radius;
|
||||||
|
-webkit-transition: all 0.5s ease-in-out;
|
||||||
|
-moz-transition: all 0.5s ease-in-out;
|
||||||
|
-ms-transition: all 0.5s ease-in-out;
|
||||||
|
-o-transition: all 0.5s ease-in-out;
|
||||||
|
transition: all 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-top-wrapper:hover {
|
||||||
|
background-color: $form-color-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-top-wrapper.show {
|
||||||
|
visibility: visible;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-top-wrapper i.fa {
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.no-search-results {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-search-results .no-search-results-icon {
|
||||||
|
font-size: 10em;
|
||||||
|
color: $form-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-search-results .no-search-results-text {
|
||||||
|
margin: 20px 0;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-search {
|
||||||
|
padding: 25px 105px 25px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-withbuttons {
|
||||||
|
padding-right: 105px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-addon .btn-group {
|
||||||
|
position: absolute;
|
||||||
|
right: 45px;
|
||||||
|
z-index: 3;
|
||||||
|
top: 13px;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-addon .btn-group .btn {
|
||||||
|
border: 1px solid rgba(255,255,255,.7) !important;
|
||||||
|
padding: 3px 12px;
|
||||||
|
color: rgba(255,255,255,.7) !important;
|
||||||
}
|
}
|
||||||
#tvList .mix{
|
|
||||||
display: none;
|
|
||||||
}
|
|
|
@ -6,71 +6,128 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
var searchSource = $("#search-template").html();
|
var searchSource = $("#search-template").html();
|
||||||
|
var albumSource = $("#album-template").html();
|
||||||
var searchTemplate = Handlebars.compile(searchSource);
|
var searchTemplate = Handlebars.compile(searchSource);
|
||||||
|
var albumTemplate = Handlebars.compile(albumSource);
|
||||||
var movieTimer = 0;
|
var movieTimer = 0;
|
||||||
var tvimer = 0;
|
var tvimer = 0;
|
||||||
|
|
||||||
movieLoad();
|
var mixItUpDefault = {
|
||||||
tvLoad();
|
animation: { enable: true },
|
||||||
|
load: {
|
||||||
|
filter: 'all',
|
||||||
|
sort: 'requestorder:desc'
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
display: 'block'
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
onMixStart: function (state, futureState) {
|
||||||
|
$('.mix', this).removeAttr('data-bound').removeData('bound'); // fix for animation issues in other tabs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initLoad();
|
||||||
|
|
||||||
|
|
||||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||||
var target = $(e.target).attr('href');
|
var target = $(e.target).attr('href');
|
||||||
var activeState = "";
|
var activeState = "";
|
||||||
if (target === "#TvShowTab") {
|
|
||||||
if ($('#movieList').mixItUp('isLoaded')) {
|
|
||||||
activeState = $('#movieList').mixItUp('getState');
|
|
||||||
$('#movieList').mixItUp('destroy');
|
|
||||||
}
|
|
||||||
if (!$('#tvList').mixItUp('isLoaded')) {
|
|
||||||
$('#tvList').mixItUp({
|
|
||||||
load: {
|
|
||||||
filter: activeState.activeFilter || 'all',
|
|
||||||
sort: activeState.activeSort || 'default:asc'
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
display: 'block'
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
var $ml = $('#movieList');
|
||||||
|
var $tvl = $('#tvList');
|
||||||
|
|
||||||
|
$('.approve-category').hide();
|
||||||
|
if (target === "#TvShowTab") {
|
||||||
|
$('#approveTVShows').show();
|
||||||
|
if ($ml.mixItUp('isLoaded')) {
|
||||||
|
activeState = $ml.mixItUp('getState');
|
||||||
|
$ml.mixItUp('destroy');
|
||||||
}
|
}
|
||||||
|
if ($tvl.mixItUp('isLoaded')) $tvl.mixItUp('destroy');
|
||||||
|
$tvl.mixItUp(mixItUpConfig(activeState)); // init or reinit
|
||||||
}
|
}
|
||||||
if (target === "#MoviesTab") {
|
if (target === "#MoviesTab") {
|
||||||
if ($('#tvList').mixItUp('isLoaded')) {
|
$('#approveMovies').show();
|
||||||
activeState = $('#tvList').mixItUp('getState');
|
if ($tvl.mixItUp('isLoaded')) {
|
||||||
$('#tvList').mixItUp('destroy');
|
activeState = $tvl.mixItUp('getState');
|
||||||
}
|
$tvl.mixItUp('destroy');
|
||||||
if (!$('#movieList').mixItUp('isLoaded')) {
|
|
||||||
$('#movieList').mixItUp({
|
|
||||||
load: {
|
|
||||||
filter: activeState.activeFilter || 'all',
|
|
||||||
sort: activeState.activeSort || 'default:asc'
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
display: 'block'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if ($ml.mixItUp('isLoaded')) $ml.mixItUp('destroy');
|
||||||
|
$ml.mixItUp(mixItUpConfig(activeState)); // init or reinit
|
||||||
}
|
}
|
||||||
|
//$('.mix[data-bound]').removeAttr('data-bound');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Approve all
|
// Approve all
|
||||||
$('#approveAll').click(function () {
|
$('#approveMovies').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var buttonId = e.target.id;
|
||||||
|
var origHtml = $(this).html();
|
||||||
|
|
||||||
|
if ($('#' + buttonId).text() === " Loading...") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingButton(buttonId, "success");
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'post',
|
type: 'post',
|
||||||
url: '/approval/approveall',
|
url: '/approval/approveallmovies',
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
if (checkJsonResponse(response)) {
|
if (checkJsonResponse(response)) {
|
||||||
generateNotify("Success! All requests approved!", "success");
|
generateNotify("Success! All Movie requests approved!", "success");
|
||||||
|
movieLoad();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (e) {
|
error: function (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
generateNotify("Something went wrong!", "danger");
|
generateNotify("Something went wrong!", "danger");
|
||||||
|
},
|
||||||
|
complete: function (e) {
|
||||||
|
finishLoading(buttonId, "success", origHtml);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
$('#approveTVShows').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var buttonId = e.target.id;
|
||||||
|
var origHtml = $(this).html();
|
||||||
|
|
||||||
|
if ($('#' + buttonId).text() === " Loading...") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingButton(buttonId, "success");
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'post',
|
||||||
|
url: '/approval/approvealltvshows',
|
||||||
|
dataType: "json",
|
||||||
|
success: function (response) {
|
||||||
|
if (checkJsonResponse(response)) {
|
||||||
|
generateNotify("Success! All TV Show requests approved!", "success");
|
||||||
|
tvLoad();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
console.log(e);
|
||||||
|
generateNotify("Something went wrong!", "danger");
|
||||||
|
},
|
||||||
|
complete: function (e) {
|
||||||
|
finishLoading(buttonId, "success", origHtml);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// filtering/sorting
|
||||||
|
$('.filter,.sort', '.dropdown-menu').click(function (e) {
|
||||||
|
var $this = $(this);
|
||||||
|
$('.fa-check-square', $this.parents('.dropdown-menu:first')).removeClass('fa-check-square').addClass('fa-square-o');
|
||||||
|
$this.children('.fa').first().removeClass('fa-square-o').addClass('fa-check-square');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Report Issue
|
// Report Issue
|
||||||
|
@ -315,36 +372,89 @@ $(document).on("click", ".change", function (e) {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function mixItUpConfig(activeState) {
|
||||||
|
var conf = mixItUpDefault;
|
||||||
|
|
||||||
|
if (activeState) {
|
||||||
|
if (activeState.activeFilter) conf['load']['filter'] = activeState.activeFilter;
|
||||||
|
if (activeState.activeSort) conf['load']['sort'] = activeState.activeSort;
|
||||||
|
}
|
||||||
|
return conf;
|
||||||
|
};
|
||||||
|
|
||||||
|
function initLoad() {
|
||||||
|
movieLoad();
|
||||||
|
tvLoad();
|
||||||
|
albumLoad();
|
||||||
|
//noResultsMusic
|
||||||
|
}
|
||||||
|
|
||||||
function movieLoad() {
|
function movieLoad() {
|
||||||
$("#movieList").html("");
|
var $ml = $('#movieList');
|
||||||
|
if ($ml.mixItUp('isLoaded')) {
|
||||||
|
activeState = $ml.mixItUp('getState');
|
||||||
|
$ml.mixItUp('destroy');
|
||||||
|
}
|
||||||
|
$ml.html("");
|
||||||
|
|
||||||
$.ajax("/requests/movies/").success(function (results) {
|
$.ajax("/requests/movies/").success(function (results) {
|
||||||
results.forEach(function (result) {
|
if (results.length > 0) {
|
||||||
var context = buildRequestContext(result, "movie");
|
results.forEach(function (result) {
|
||||||
|
var context = buildRequestContext(result, "movie");
|
||||||
var html = searchTemplate(context);
|
var html = searchTemplate(context);
|
||||||
$("#movieList").append(html);
|
$ml.append(html);
|
||||||
});
|
});
|
||||||
$('#movieList').mixItUp({
|
}
|
||||||
layout: {
|
else {
|
||||||
display: 'block'
|
$ml.html(noResultsHtml.format("movie"));
|
||||||
},
|
}
|
||||||
load: {
|
$ml.mixItUp(mixItUpConfig());
|
||||||
filter: 'all'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function tvLoad() {
|
function tvLoad() {
|
||||||
$("#tvList").html("");
|
var $tvl = $('#tvList');
|
||||||
|
if ($tvl.mixItUp('isLoaded')) {
|
||||||
|
activeState = $tvl.mixItUp('getState');
|
||||||
|
$tvl.mixItUp('destroy');
|
||||||
|
}
|
||||||
|
$tvl.html("");
|
||||||
|
|
||||||
$.ajax("/requests/tvshows/").success(function (results) {
|
$.ajax("/requests/tvshows/").success(function (results) {
|
||||||
results.forEach(function (result) {
|
if (results.length > 0) {
|
||||||
var context = buildRequestContext(result, "tv");
|
results.forEach(function (result) {
|
||||||
var html = searchTemplate(context);
|
var context = buildRequestContext(result, "tv");
|
||||||
$("#tvList").append(html);
|
var html = searchTemplate(context);
|
||||||
});
|
$tvl.append(html);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$tvl.html(noResultsHtml.format("tv show"));
|
||||||
|
}
|
||||||
|
$tvl.mixItUp(mixItUpConfig());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function albumLoad() {
|
||||||
|
var $albumL = $('#MusicList');
|
||||||
|
if ($albumL.mixItUp('isLoaded')) {
|
||||||
|
activeState = $albumL.mixItUp('getState');
|
||||||
|
$albumL.mixItUp('destroy');
|
||||||
|
}
|
||||||
|
$albumL.html("");
|
||||||
|
|
||||||
|
$.ajax("/requests/albums/").success(function (results) {
|
||||||
|
if (results.length > 0) {
|
||||||
|
results.forEach(function (result) {
|
||||||
|
var context = buildRequestContext(result, "album");
|
||||||
|
var html = searchTemplate(context);
|
||||||
|
$albumL.append(html);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$albumL.html(noResultsMusic.format("albums"));
|
||||||
|
}
|
||||||
|
$albumL.mixItUp(mixItUpConfig());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -359,9 +469,11 @@ function buildRequestContext(result, type) {
|
||||||
type: type,
|
type: type,
|
||||||
status: result.status,
|
status: result.status,
|
||||||
releaseDate: result.releaseDate,
|
releaseDate: result.releaseDate,
|
||||||
|
releaseDateTicks: result.releaseDateTicks,
|
||||||
approved: result.approved,
|
approved: result.approved,
|
||||||
requestedBy: result.requestedBy,
|
requestedUsers: result.requestedUsers ? result.requestedUsers.join(', ') : '',
|
||||||
requestedDate: result.requestedDate,
|
requestedDate: result.requestedDate,
|
||||||
|
requestedDateTicks: result.requestedDateTicks,
|
||||||
available: result.available,
|
available: result.available,
|
||||||
admin: result.admin,
|
admin: result.admin,
|
||||||
issues: result.issues,
|
issues: result.issues,
|
||||||
|
@ -369,20 +481,11 @@ function buildRequestContext(result, type) {
|
||||||
requestId: result.id,
|
requestId: result.id,
|
||||||
adminNote: result.adminNotes,
|
adminNote: result.adminNotes,
|
||||||
imdb: result.imdbId,
|
imdb: result.imdbId,
|
||||||
seriesRequested: result.tvSeriesRequestType
|
seriesRequested: result.tvSeriesRequestType,
|
||||||
|
coverArtUrl: result.coverArtUrl,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
function startFilter(elementId) {
|
|
||||||
$('#'+element).mixItUp({
|
|
||||||
load: {
|
|
||||||
filter: activeState.activeFilter || 'all',
|
|
||||||
sort: activeState.activeSort || 'default:asc'
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
display: 'block'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -6,27 +6,39 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
var searchSource = $("#search-template").html();
|
var searchSource = $("#search-template").html();
|
||||||
|
var musicSource = $("#music-template").html();
|
||||||
var searchTemplate = Handlebars.compile(searchSource);
|
var searchTemplate = Handlebars.compile(searchSource);
|
||||||
var movieTimer = 0;
|
var musicTemplate = Handlebars.compile(musicSource);
|
||||||
var tvimer = 0;
|
|
||||||
|
var searchTimer = 0;
|
||||||
|
|
||||||
// Type in movie search
|
// Type in movie search
|
||||||
$("#movieSearchContent").on("input", function () {
|
$("#movieSearchContent").on("input", function () {
|
||||||
if (movieTimer) {
|
if (searchTimer) {
|
||||||
clearTimeout(movieTimer);
|
clearTimeout(searchTimer);
|
||||||
}
|
}
|
||||||
$('#movieSearchButton').attr("class","fa fa-spinner fa-spin");
|
$('#movieSearchButton').attr("class","fa fa-spinner fa-spin");
|
||||||
movieTimer = setTimeout(movieSearch, 400);
|
searchTimer = setTimeout(movieSearch, 400);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#moviesComingSoon').on('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
moviesComingSoon();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#moviesInTheaters').on('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
moviesInTheaters();
|
||||||
|
});
|
||||||
|
|
||||||
// Type in TV search
|
// Type in TV search
|
||||||
$("#tvSearchContent").on("input", function () {
|
$("#tvSearchContent").on("input", function () {
|
||||||
if (tvimer) {
|
if (searchTimer) {
|
||||||
clearTimeout(tvimer);
|
clearTimeout(searchTimer);
|
||||||
}
|
}
|
||||||
$('#tvSearchButton').attr("class", "fa fa-spinner fa-spin");
|
$('#tvSearchButton').attr("class", "fa fa-spinner fa-spin");
|
||||||
tvimer = setTimeout(tvSearch, 400);
|
searchTimer = setTimeout(tvSearch, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Click TV dropdown option
|
// Click TV dropdown option
|
||||||
|
@ -60,6 +72,16 @@ $(document).on("click", ".dropdownTv", function (e) {
|
||||||
sendRequestAjax(data, type, url, buttonId);
|
sendRequestAjax(data, type, url, buttonId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Search Music
|
||||||
|
$("#musicSearchContent").on("input", function () {
|
||||||
|
if (searchTimer) {
|
||||||
|
clearTimeout(searchTimer);
|
||||||
|
}
|
||||||
|
$('#musicSearchButton').attr("class", "fa fa-spinner fa-spin");
|
||||||
|
searchTimer = setTimeout(musicSearch, 400);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
// Click Request for movie
|
// Click Request for movie
|
||||||
$(document).on("click", ".requestMovie", function (e) {
|
$(document).on("click", ".requestMovie", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -82,6 +104,28 @@ $(document).on("click", ".requestMovie", function (e) {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Click Request for album
|
||||||
|
$(document).on("click", ".requestAlbum", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var buttonId = e.target.id;
|
||||||
|
if ($("#" + buttonId).attr('disabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#" + buttonId).prop("disabled", true);
|
||||||
|
loadingButton(buttonId, "primary");
|
||||||
|
|
||||||
|
|
||||||
|
var $form = $('#form' + buttonId);
|
||||||
|
|
||||||
|
var type = $form.prop('method');
|
||||||
|
var url = $form.prop('action');
|
||||||
|
var data = $form.serialize();
|
||||||
|
|
||||||
|
sendRequestAjax(data, type, url, buttonId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
function sendRequestAjax(data, type, url, buttonId) {
|
function sendRequestAjax(data, type, url, buttonId) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: type,
|
type: type,
|
||||||
|
@ -112,10 +156,23 @@ function sendRequestAjax(data, type, url, buttonId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function movieSearch() {
|
function movieSearch() {
|
||||||
$("#movieList").html("");
|
|
||||||
var query = $("#movieSearchContent").val();
|
var query = $("#movieSearchContent").val();
|
||||||
|
getMovies("/search/movie/" + query);
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax("/search/movie/" + query).success(function (results) {
|
function moviesComingSoon() {
|
||||||
|
getMovies("/search/movie/upcoming");
|
||||||
|
}
|
||||||
|
|
||||||
|
function moviesInTheaters() {
|
||||||
|
getMovies("/search/movie/playing");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMovies(url) {
|
||||||
|
$("#movieList").html("");
|
||||||
|
|
||||||
|
|
||||||
|
$.ajax(url).success(function (results) {
|
||||||
if (results.length > 0) {
|
if (results.length > 0) {
|
||||||
results.forEach(function(result) {
|
results.forEach(function(result) {
|
||||||
var context = buildMovieContext(result);
|
var context = buildMovieContext(result);
|
||||||
|
@ -124,15 +181,22 @@ function movieSearch() {
|
||||||
$("#movieList").append(html);
|
$("#movieList").append(html);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$("#movieList").html(noResultsHtml);
|
||||||
|
}
|
||||||
$('#movieSearchButton').attr("class","fa fa-search");
|
$('#movieSearchButton').attr("class","fa fa-search");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function tvSearch() {
|
function tvSearch() {
|
||||||
$("#tvList").html("");
|
|
||||||
var query = $("#tvSearchContent").val();
|
var query = $("#tvSearchContent").val();
|
||||||
|
getTvShows("/search/tv/" + query);
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax("/search/tv/" + query).success(function (results) {
|
function getTvShows(url) {
|
||||||
|
$("#tvList").html("");
|
||||||
|
|
||||||
|
$.ajax(url).success(function (results) {
|
||||||
if (results.length > 0) {
|
if (results.length > 0) {
|
||||||
results.forEach(function(result) {
|
results.forEach(function(result) {
|
||||||
var context = buildTvShowContext(result);
|
var context = buildTvShowContext(result);
|
||||||
|
@ -140,10 +204,36 @@ function tvSearch() {
|
||||||
$("#tvList").append(html);
|
$("#tvList").append(html);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$("#tvList").html(noResultsHtml);
|
||||||
|
}
|
||||||
$('#tvSearchButton').attr("class", "fa fa-search");
|
$('#tvSearchButton').attr("class", "fa fa-search");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function musicSearch() {
|
||||||
|
var query = $("#musicSearchContent").val();
|
||||||
|
getMusic("/search/music/" + query);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMusic(url) {
|
||||||
|
$("#musicList").html("");
|
||||||
|
|
||||||
|
$.ajax(url).success(function (results) {
|
||||||
|
if (results.length > 0) {
|
||||||
|
results.forEach(function (result) {
|
||||||
|
var context = buildMusicContext(result);
|
||||||
|
|
||||||
|
var html = musicTemplate(context);
|
||||||
|
$("#musicList").append(html);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#musicList").html(noResultsMusic);
|
||||||
|
}
|
||||||
|
$('#musicSearchButton').attr("class", "fa fa-search");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function buildMovieContext(result) {
|
function buildMovieContext(result) {
|
||||||
var date = new Date(result.releaseDate);
|
var date = new Date(result.releaseDate);
|
||||||
|
@ -177,3 +267,21 @@ function buildTvShowContext(result) {
|
||||||
};
|
};
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildMusicContext(result) {
|
||||||
|
|
||||||
|
var context = {
|
||||||
|
id: result.id,
|
||||||
|
title: result.title,
|
||||||
|
overview: result.overview,
|
||||||
|
year: result.releaseDate,
|
||||||
|
type: "album",
|
||||||
|
trackCount: result.trackCount,
|
||||||
|
coverArtUrl: result.coverArtUrl,
|
||||||
|
artist: result.artist,
|
||||||
|
releaseType: result.releaseType,
|
||||||
|
country: result.country
|
||||||
|
};
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
function generateNotify(message, type) {
|
String.prototype.format = String.prototype.f = function () {
|
||||||
|
var s = this,
|
||||||
|
i = arguments.length;
|
||||||
|
|
||||||
|
while (i--) {
|
||||||
|
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNotify(message, type) {
|
||||||
// type = danger, warning, info, successs
|
// type = danger, warning, info, successs
|
||||||
$.notify({
|
$.notify({
|
||||||
// options
|
// options
|
||||||
|
@ -34,4 +44,9 @@ function finishLoading(elementId, originalCss, html) {
|
||||||
$('#' + elementId).removeClass("btn-primary-outline");
|
$('#' + elementId).removeClass("btn-primary-outline");
|
||||||
$('#' + elementId).addClass("btn-" + originalCss + "-outline");
|
$('#' + elementId).addClass("btn-" + originalCss + "-outline");
|
||||||
$('#' + elementId).html(html);
|
$('#' + elementId).html(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var noResultsHtml = "<div class='no-search-results'>" +
|
||||||
|
"<i class='fa fa-film no-search-results-icon'></i><div class='no-search-results-text'>Sorry, we didn't find any results!</div></div>";
|
||||||
|
var noResultsMusic = "<div class='no-search-results'>" +
|
||||||
|
"<i class='fa fa-headphones no-search-results-icon'></i><div class='no-search-results-text'>Sorry, we didn't find any results!</div></div>";
|
|
@ -24,17 +24,13 @@
|
||||||
// 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;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using PlexRequests.Api.Interfaces;
|
using PlexRequests.Api.Interfaces;
|
||||||
using PlexRequests.Api.Models.SickRage;
|
using PlexRequests.Api.Models.SickRage;
|
||||||
using PlexRequests.Api.Models.Sonarr;
|
using PlexRequests.Api.Models.Sonarr;
|
||||||
using PlexRequests.Core;
|
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
using PlexRequests.Store;
|
using PlexRequests.Store;
|
||||||
using PlexRequests.UI.Models;
|
|
||||||
|
|
||||||
namespace PlexRequests.UI.Helpers
|
namespace PlexRequests.UI.Helpers
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,12 +12,12 @@ namespace PlexRequests.UI.Jobs
|
||||||
//typeof(AvailabilityUpdateService);
|
//typeof(AvailabilityUpdateService);
|
||||||
var container = TinyIoCContainer.Current;
|
var container = TinyIoCContainer.Current;
|
||||||
|
|
||||||
var a= container.ResolveAll(typeof(T));
|
var a= container.Resolve(typeof(T));
|
||||||
|
|
||||||
object outT;
|
object outT;
|
||||||
container.TryResolve(typeof(T), out outT);
|
container.TryResolve(typeof(T), out outT);
|
||||||
|
|
||||||
return (T)outT;
|
return (T)a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -37,11 +37,13 @@ namespace PlexRequests.UI.Models
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string PosterPath { get; set; }
|
public string PosterPath { get; set; }
|
||||||
public string ReleaseDate { get; set; }
|
public string ReleaseDate { get; set; }
|
||||||
|
public long ReleaseDateTicks { get; set; }
|
||||||
public RequestType Type { get; set; }
|
public RequestType Type { get; set; }
|
||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public bool Approved { get; set; }
|
public bool Approved { get; set; }
|
||||||
public string RequestedBy { get; set; }
|
public string[] RequestedUsers { get; set; }
|
||||||
public string RequestedDate { get; set; }
|
public string RequestedDate { get; set; }
|
||||||
|
public long RequestedDateTicks { get; set; }
|
||||||
public string ReleaseYear { get; set; }
|
public string ReleaseYear { get; set; }
|
||||||
public bool Available { get; set; }
|
public bool Available { get; set; }
|
||||||
public bool Admin { get; set; }
|
public bool Admin { get; set; }
|
||||||
|
@ -49,5 +51,6 @@ namespace PlexRequests.UI.Models
|
||||||
public string OtherMessage { get; set; }
|
public string OtherMessage { get; set; }
|
||||||
public string AdminNotes { get; set; }
|
public string AdminNotes { get; set; }
|
||||||
public string TvSeriesRequestType { get; set; }
|
public string TvSeriesRequestType { get; set; }
|
||||||
|
public string MusicBrainzId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
PlexRequests.UI/Models/SearchMusicViewModel.cs
Normal file
41
PlexRequests.UI/Models/SearchMusicViewModel.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: SearchMusicViewModel.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
namespace PlexRequests.UI.Models
|
||||||
|
{
|
||||||
|
public class SearchMusicViewModel
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Overview { get; set; }
|
||||||
|
public string CoverArtUrl { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Artist { get; set; }
|
||||||
|
public string ReleaseDate { get; set; }
|
||||||
|
public int TrackCount { get; set; }
|
||||||
|
public string ReleaseType { get; set; }
|
||||||
|
public string Country { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,5 +29,6 @@ namespace PlexRequests.UI.Models
|
||||||
public class SessionKeys
|
public class SessionKeys
|
||||||
{
|
{
|
||||||
public const string UsernameKey = "Username";
|
public const string UsernameKey = "Username";
|
||||||
|
public const string ClientDateTimeOffsetKey = "ClientDateTimeOffset";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Dynamic;
|
using System.Dynamic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Web.UI.HtmlControls;
|
||||||
|
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using MarkdownSharp;
|
using MarkdownSharp;
|
||||||
|
|
||||||
|
@ -51,12 +53,13 @@ using PlexRequests.Store.Models;
|
||||||
using PlexRequests.Store.Repository;
|
using PlexRequests.Store.Repository;
|
||||||
using PlexRequests.UI.Helpers;
|
using PlexRequests.UI.Helpers;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace PlexRequests.UI.Modules
|
namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
public class AdminModule : NancyModule
|
public class AdminModule : NancyModule
|
||||||
{
|
{
|
||||||
private ISettingsService<PlexRequestSettings> RpService { get; }
|
private ISettingsService<PlexRequestSettings> PrService { get; }
|
||||||
private ISettingsService<CouchPotatoSettings> CpService { get; }
|
private ISettingsService<CouchPotatoSettings> CpService { get; }
|
||||||
private ISettingsService<AuthenticationSettings> AuthService { get; }
|
private ISettingsService<AuthenticationSettings> AuthService { get; }
|
||||||
private ISettingsService<PlexSettings> PlexService { get; }
|
private ISettingsService<PlexSettings> PlexService { get; }
|
||||||
|
@ -65,6 +68,7 @@ namespace PlexRequests.UI.Modules
|
||||||
private ISettingsService<EmailNotificationSettings> EmailService { get; }
|
private ISettingsService<EmailNotificationSettings> EmailService { get; }
|
||||||
private ISettingsService<PushbulletNotificationSettings> PushbulletService { get; }
|
private ISettingsService<PushbulletNotificationSettings> PushbulletService { get; }
|
||||||
private ISettingsService<PushoverNotificationSettings> PushoverService { get; }
|
private ISettingsService<PushoverNotificationSettings> PushoverService { get; }
|
||||||
|
private ISettingsService<HeadphonesSettings> HeadphonesService { get; }
|
||||||
private IPlexApi PlexApi { get; }
|
private IPlexApi PlexApi { get; }
|
||||||
private ISonarrApi SonarrApi { get; }
|
private ISonarrApi SonarrApi { get; }
|
||||||
private IPushbulletApi PushbulletApi { get; }
|
private IPushbulletApi PushbulletApi { get; }
|
||||||
|
@ -74,7 +78,7 @@ namespace PlexRequests.UI.Modules
|
||||||
private INotificationService NotificationService { get; }
|
private INotificationService NotificationService { get; }
|
||||||
|
|
||||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
public AdminModule(ISettingsService<PlexRequestSettings> rpService,
|
public AdminModule(ISettingsService<PlexRequestSettings> prService,
|
||||||
ISettingsService<CouchPotatoSettings> cpService,
|
ISettingsService<CouchPotatoSettings> cpService,
|
||||||
ISettingsService<AuthenticationSettings> auth,
|
ISettingsService<AuthenticationSettings> auth,
|
||||||
ISettingsService<PlexSettings> plex,
|
ISettingsService<PlexSettings> plex,
|
||||||
|
@ -89,9 +93,10 @@ namespace PlexRequests.UI.Modules
|
||||||
ISettingsService<PushoverNotificationSettings> pushoverSettings,
|
ISettingsService<PushoverNotificationSettings> pushoverSettings,
|
||||||
IPushoverApi pushoverApi,
|
IPushoverApi pushoverApi,
|
||||||
IRepository<LogEntity> logsRepo,
|
IRepository<LogEntity> logsRepo,
|
||||||
INotificationService notify) : base("admin")
|
INotificationService notify,
|
||||||
|
ISettingsService<HeadphonesSettings> headphones) : base("admin")
|
||||||
{
|
{
|
||||||
RpService = rpService;
|
PrService = prService;
|
||||||
CpService = cpService;
|
CpService = cpService;
|
||||||
AuthService = auth;
|
AuthService = auth;
|
||||||
PlexService = plex;
|
PlexService = plex;
|
||||||
|
@ -107,6 +112,7 @@ namespace PlexRequests.UI.Modules
|
||||||
PushoverService = pushoverSettings;
|
PushoverService = pushoverSettings;
|
||||||
PushoverApi = pushoverApi;
|
PushoverApi = pushoverApi;
|
||||||
NotificationService = notify;
|
NotificationService = notify;
|
||||||
|
HeadphonesService = headphones;
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
this.RequiresAuthentication();
|
this.RequiresAuthentication();
|
||||||
|
@ -139,18 +145,24 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
Get["/emailnotification"] = _ => EmailNotifications();
|
Get["/emailnotification"] = _ => EmailNotifications();
|
||||||
Post["/emailnotification"] = _ => SaveEmailNotifications();
|
Post["/emailnotification"] = _ => SaveEmailNotifications();
|
||||||
|
Post["/testemailnotification"] = _ => TestEmailNotifications();
|
||||||
Get["/status"] = _ => Status();
|
Get["/status"] = _ => Status();
|
||||||
|
|
||||||
Get["/pushbulletnotification"] = _ => PushbulletNotifications();
|
Get["/pushbulletnotification"] = _ => PushbulletNotifications();
|
||||||
Post["/pushbulletnotification"] = _ => SavePushbulletNotifications();
|
Post["/pushbulletnotification"] = _ => SavePushbulletNotifications();
|
||||||
|
Post["/testpushbulletnotification"] = _ => TestPushbulletNotifications();
|
||||||
|
|
||||||
Get["/pushovernotification"] = _ => PushoverNotifications();
|
Get["/pushovernotification"] = _ => PushoverNotifications();
|
||||||
Post["/pushovernotification"] = _ => SavePushoverNotifications();
|
Post["/pushovernotification"] = _ => SavePushoverNotifications();
|
||||||
|
Post["/testpushovernotification"] = _ => TestPushoverNotifications();
|
||||||
|
|
||||||
Get["/logs"] = _ => Logs();
|
Get["/logs"] = _ => Logs();
|
||||||
Get["/loglevel"] = _ => GetLogLevels();
|
Get["/loglevel"] = _ => GetLogLevels();
|
||||||
Post["/loglevel"] = _ => UpdateLogLevels(Request.Form.level);
|
Post["/loglevel"] = _ => UpdateLogLevels(Request.Form.level);
|
||||||
Get["/loadlogs"] = _ => LoadLogs();
|
Get["/loadlogs"] = _ => LoadLogs();
|
||||||
|
|
||||||
|
Get["/headphones"] = _ => Headphones();
|
||||||
|
Post["/headphones"] = _ => SaveHeadphones();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Negotiator Authentication()
|
private Negotiator Authentication()
|
||||||
|
@ -174,7 +186,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private Negotiator Admin()
|
private Negotiator Admin()
|
||||||
{
|
{
|
||||||
var settings = RpService.GetSettings();
|
var settings = PrService.GetSettings();
|
||||||
Log.Trace("Getting Settings:");
|
Log.Trace("Getting Settings:");
|
||||||
Log.Trace(settings.DumpJson());
|
Log.Trace(settings.DumpJson());
|
||||||
|
|
||||||
|
@ -185,7 +197,7 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
var model = this.Bind<PlexRequestSettings>();
|
var model = this.Bind<PlexRequestSettings>();
|
||||||
|
|
||||||
RpService.SaveSettings(model);
|
PrService.SaveSettings(model);
|
||||||
|
|
||||||
|
|
||||||
return Context.GetRedirect("~/admin");
|
return Context.GetRedirect("~/admin");
|
||||||
|
@ -372,6 +384,37 @@ namespace PlexRequests.UI.Modules
|
||||||
return View["EmailNotifications", settings];
|
return View["EmailNotifications", settings];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response TestEmailNotifications()
|
||||||
|
{
|
||||||
|
var settings = this.Bind<EmailNotificationSettings>();
|
||||||
|
var valid = this.Validate(settings);
|
||||||
|
if (!valid.IsValid)
|
||||||
|
{
|
||||||
|
return Response.AsJson(valid.SendJsonError());
|
||||||
|
}
|
||||||
|
var notificationModel = new NotificationModel
|
||||||
|
{
|
||||||
|
NotificationType = NotificationType.Test,
|
||||||
|
DateTime = DateTime.Now
|
||||||
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NotificationService.Subscribe(new EmailMessageNotification(EmailService));
|
||||||
|
settings.Enabled = true;
|
||||||
|
NotificationService.Publish(notificationModel, settings);
|
||||||
|
Log.Info("Sent email notification test");
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to subscribe and publish test Email Notification");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
NotificationService.UnSubscribe(new EmailMessageNotification(EmailService));
|
||||||
|
}
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" });
|
||||||
|
}
|
||||||
|
|
||||||
private Response SaveEmailNotifications()
|
private Response SaveEmailNotifications()
|
||||||
{
|
{
|
||||||
var settings = this.Bind<EmailNotificationSettings>();
|
var settings = this.Bind<EmailNotificationSettings>();
|
||||||
|
@ -440,6 +483,37 @@ namespace PlexRequests.UI.Modules
|
||||||
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
|
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response TestPushbulletNotifications()
|
||||||
|
{
|
||||||
|
var settings = this.Bind<PushbulletNotificationSettings>();
|
||||||
|
var valid = this.Validate(settings);
|
||||||
|
if (!valid.IsValid)
|
||||||
|
{
|
||||||
|
return Response.AsJson(valid.SendJsonError());
|
||||||
|
}
|
||||||
|
var notificationModel = new NotificationModel
|
||||||
|
{
|
||||||
|
NotificationType = NotificationType.Test,
|
||||||
|
DateTime = DateTime.Now
|
||||||
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
|
||||||
|
settings.Enabled = true;
|
||||||
|
NotificationService.Publish(notificationModel, settings);
|
||||||
|
Log.Info("Sent pushbullet notification test");
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to subscribe and publish test Pushbullet Notification");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
|
||||||
|
}
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" });
|
||||||
|
}
|
||||||
|
|
||||||
private Negotiator PushoverNotifications()
|
private Negotiator PushoverNotifications()
|
||||||
{
|
{
|
||||||
var settings = PushoverService.GetSettings();
|
var settings = PushoverService.GetSettings();
|
||||||
|
@ -472,6 +546,37 @@ namespace PlexRequests.UI.Modules
|
||||||
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
|
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response TestPushoverNotifications()
|
||||||
|
{
|
||||||
|
var settings = this.Bind<PushoverNotificationSettings>();
|
||||||
|
var valid = this.Validate(settings);
|
||||||
|
if (!valid.IsValid)
|
||||||
|
{
|
||||||
|
return Response.AsJson(valid.SendJsonError());
|
||||||
|
}
|
||||||
|
var notificationModel = new NotificationModel
|
||||||
|
{
|
||||||
|
NotificationType = NotificationType.Test,
|
||||||
|
DateTime = DateTime.Now
|
||||||
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService));
|
||||||
|
settings.Enabled = true;
|
||||||
|
NotificationService.Publish(notificationModel, settings);
|
||||||
|
Log.Info("Sent pushover notification test");
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to subscribe and publish test Pushover Notification");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService));
|
||||||
|
}
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" });
|
||||||
|
}
|
||||||
|
|
||||||
private Response GetCpProfiles()
|
private Response GetCpProfiles()
|
||||||
{
|
{
|
||||||
var settings = this.Bind<CouchPotatoSettings>();
|
var settings = this.Bind<CouchPotatoSettings>();
|
||||||
|
@ -509,5 +614,32 @@ namespace PlexRequests.UI.Modules
|
||||||
LoggingHelper.ReconfigureLogLevel(newLevel);
|
LoggingHelper.ReconfigureLogLevel(newLevel);
|
||||||
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}"});
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Negotiator Headphones()
|
||||||
|
{
|
||||||
|
var settings = HeadphonesService.GetSettings();
|
||||||
|
return View["Headphones", settings];
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response SaveHeadphones()
|
||||||
|
{
|
||||||
|
var settings = this.Bind<HeadphonesSettings>();
|
||||||
|
|
||||||
|
var valid = this.Validate(settings);
|
||||||
|
if (!valid.IsValid)
|
||||||
|
{
|
||||||
|
var error = valid.SendJsonError();
|
||||||
|
Log.Info("Error validating Headphones settings, message: {0}", error.Message);
|
||||||
|
return Response.AsJson(error);
|
||||||
|
}
|
||||||
|
Log.Trace(settings.DumpJson());
|
||||||
|
|
||||||
|
var result = HeadphonesService.SaveSettings(settings);
|
||||||
|
|
||||||
|
Log.Info("Saved headphones settings, result: {0}", result);
|
||||||
|
return Response.AsJson(result
|
||||||
|
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Headphones!" }
|
||||||
|
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -57,6 +57,7 @@ namespace PlexRequests.UI.Modules
|
||||||
Post["/sonarr"] = _ => SonarrTest();
|
Post["/sonarr"] = _ => SonarrTest();
|
||||||
Post["/plex"] = _ => PlexTest();
|
Post["/plex"] = _ => PlexTest();
|
||||||
Post["/sickrage"] = _ => SickRageTest();
|
Post["/sickrage"] = _ => SickRageTest();
|
||||||
|
Post["/headphones"] = _ => HeadphonesTest();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,5 +169,10 @@ namespace PlexRequests.UI.Modules
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response HeadphonesTest()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException(); //TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -61,6 +61,8 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
Post["/approve"] = parameters => Approve((int)Request.Form.requestid);
|
Post["/approve"] = parameters => Approve((int)Request.Form.requestid);
|
||||||
Post["/approveall"] = x => ApproveAll();
|
Post["/approveall"] = x => ApproveAll();
|
||||||
|
Post["/approveallmovies"] = x => ApproveAllMovies();
|
||||||
|
Post["/approvealltvshows"] = x => ApproveAllTVShows();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRequestService Service { get; }
|
private IRequestService Service { get; }
|
||||||
|
@ -131,7 +133,7 @@ namespace PlexRequests.UI.Modules
|
||||||
return Response.AsJson(new JsonResponseModel
|
return Response.AsJson(new JsonResponseModel
|
||||||
{
|
{
|
||||||
Result = false,
|
Result = false,
|
||||||
Message = "Could not add the series to Sonarr"
|
Message = result.ErrorMessage ?? "Could not add the series to Sonarr"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +218,56 @@ namespace PlexRequests.UI.Modules
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response ApproveAllMovies()
|
||||||
|
{
|
||||||
|
if (!Context.CurrentUser.IsAuthenticated())
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var requests = Service.GetAll().Where(x => x.CanApprove && x.Type == RequestType.Movie);
|
||||||
|
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
|
||||||
|
if (!requestedModels.Any())
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no movie requests to approve. Please refresh." });
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return UpdateRequests(requestedModels);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Fatal(e);
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response ApproveAllTVShows()
|
||||||
|
{
|
||||||
|
if (!Context.CurrentUser.IsAuthenticated())
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var requests = Service.GetAll().Where(x => x.CanApprove && x.Type == RequestType.TvShow);
|
||||||
|
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
|
||||||
|
if (!requestedModels.Any())
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no tv show requests to approve. Please refresh." });
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return UpdateRequests(requestedModels);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Fatal(e);
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Approves all.
|
/// Approves all.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -227,23 +279,35 @@ namespace PlexRequests.UI.Modules
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." });
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." });
|
||||||
}
|
}
|
||||||
|
|
||||||
var requests = Service.GetAll().Where(x => x.Approved == false);
|
var requests = Service.GetAll().Where(x => x.CanApprove);
|
||||||
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
|
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
|
||||||
if (!requestedModels.Any())
|
if (!requestedModels.Any())
|
||||||
{
|
{
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." });
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return UpdateRequests(requestedModels);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Fatal(e);
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response UpdateRequests(RequestedModel[] requestedModels)
|
||||||
|
{
|
||||||
var cpSettings = CpService.GetSettings();
|
var cpSettings = CpService.GetSettings();
|
||||||
|
|
||||||
|
|
||||||
var updatedRequests = new List<RequestedModel>();
|
var updatedRequests = new List<RequestedModel>();
|
||||||
foreach (var r in requestedModels)
|
foreach (var r in requestedModels)
|
||||||
{
|
{
|
||||||
if (r.Type == RequestType.Movie)
|
if (r.Type == RequestType.Movie)
|
||||||
{
|
{
|
||||||
var result = SendMovie(cpSettings, r, CpApi);
|
var res = SendMovie(cpSettings, r, CpApi);
|
||||||
if (result)
|
if (res)
|
||||||
{
|
{
|
||||||
r.Approved = true;
|
r.Approved = true;
|
||||||
updatedRequests.Add(r);
|
updatedRequests.Add(r);
|
||||||
|
@ -260,8 +324,8 @@ namespace PlexRequests.UI.Modules
|
||||||
var sonarr = SonarrSettings.GetSettings();
|
var sonarr = SonarrSettings.GetSettings();
|
||||||
if (sr.Enabled)
|
if (sr.Enabled)
|
||||||
{
|
{
|
||||||
var result = sender.SendToSickRage(sr, r);
|
var res = sender.SendToSickRage(sr, r);
|
||||||
if (result?.result == "success")
|
if (res?.result == "success")
|
||||||
{
|
{
|
||||||
r.Approved = true;
|
r.Approved = true;
|
||||||
updatedRequests.Add(r);
|
updatedRequests.Add(r);
|
||||||
|
@ -269,14 +333,14 @@ namespace PlexRequests.UI.Modules
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Error("Could not approve and send the TV {0} to SickRage!", r.Title);
|
Log.Error("Could not approve and send the TV {0} to SickRage!", r.Title);
|
||||||
Log.Error("SickRage Message: {0}", result?.message);
|
Log.Error("SickRage Message: {0}", res?.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sonarr.Enabled)
|
if (sonarr.Enabled)
|
||||||
{
|
{
|
||||||
var result = sender.SendToSonarr(sonarr, r);
|
var res = sender.SendToSonarr(sonarr, r);
|
||||||
if (result != null)
|
if (!string.IsNullOrEmpty(res?.title))
|
||||||
{
|
{
|
||||||
r.Approved = true;
|
r.Approved = true;
|
||||||
updatedRequests.Add(r);
|
updatedRequests.Add(r);
|
||||||
|
@ -284,6 +348,7 @@ namespace PlexRequests.UI.Modules
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Error("Could not approve and send the TV {0} to Sonarr!", r.Title);
|
Log.Error("Could not approve and send the TV {0} to Sonarr!", r.Title);
|
||||||
|
Log.Error("Error message: {0}", res?.ErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,17 +356,16 @@ namespace PlexRequests.UI.Modules
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
var result = Service.BatchUpdate(updatedRequests); return Response.AsJson(result
|
var result = Service.BatchUpdate(updatedRequests);
|
||||||
|
return Response.AsJson(result
|
||||||
? new JsonResponseModel { Result = true }
|
? new JsonResponseModel { Result = true }
|
||||||
: new JsonResponseModel { Result = false, Message = "We could not approve all of the requests. Please try again or check the logs." });
|
: new JsonResponseModel { Result = false, Message = "We could not approve all of the requests. Please try again or check the logs." });
|
||||||
|
}
|
||||||
}
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Fatal(e);
|
Log.Fatal(e);
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp)
|
private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp)
|
||||||
|
|
|
@ -28,21 +28,50 @@
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using Nancy.Extensions;
|
using Nancy.Extensions;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace PlexRequests.UI.Modules
|
namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
public class BaseModule : NancyModule
|
public class BaseModule : NancyModule
|
||||||
{
|
{
|
||||||
|
private string _username;
|
||||||
|
private int _dateTimeOffset = -1;
|
||||||
|
|
||||||
|
protected string Username
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_username))
|
||||||
|
{
|
||||||
|
_username = Session[SessionKeys.UsernameKey].ToString();
|
||||||
|
}
|
||||||
|
return _username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int DateTimeOffset
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_dateTimeOffset == -1)
|
||||||
|
{
|
||||||
|
_dateTimeOffset = Session[SessionKeys.ClientDateTimeOffsetKey] != null ?
|
||||||
|
(int)Session[SessionKeys.ClientDateTimeOffsetKey] : (new DateTimeOffset().Offset).Minutes;
|
||||||
|
}
|
||||||
|
return _dateTimeOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public BaseModule()
|
public BaseModule()
|
||||||
{
|
{
|
||||||
Before += (ctx)=> CheckAuth();
|
Before += (ctx) => CheckAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseModule(string modulePath) : base(modulePath)
|
public BaseModule(string modulePath) : base(modulePath)
|
||||||
{
|
{
|
||||||
Before += (ctx) => CheckAuth();
|
Before += (ctx) => CheckAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Response CheckAuth()
|
private Response CheckAuth()
|
||||||
{
|
{
|
||||||
|
|
|
@ -60,6 +60,7 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
var username = (string)Request.Form.Username;
|
var username = (string)Request.Form.Username;
|
||||||
var password = (string)Request.Form.Password;
|
var password = (string)Request.Form.Password;
|
||||||
|
var dtOffset = (int)Request.Form.DateTimeOffset;
|
||||||
|
|
||||||
var userId = UserMapper.ValidateUser(username, password);
|
var userId = UserMapper.ValidateUser(username, password);
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ namespace PlexRequests.UI.Modules
|
||||||
expiry = DateTime.Now.AddDays(7);
|
expiry = DateTime.Now.AddDays(7);
|
||||||
}
|
}
|
||||||
Session[SessionKeys.UsernameKey] = username;
|
Session[SessionKeys.UsernameKey] = username;
|
||||||
|
Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset;
|
||||||
return this.LoginAndRedirect(userId.Value, expiry);
|
return this.LoginAndRedirect(userId.Value, expiry);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -40,12 +40,12 @@ using PlexRequests.Services.Interfaces;
|
||||||
using PlexRequests.Services.Notification;
|
using PlexRequests.Services.Notification;
|
||||||
using PlexRequests.Store;
|
using PlexRequests.Store;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
using PlexRequests.Helpers;
|
||||||
|
|
||||||
namespace PlexRequests.UI.Modules
|
namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
public class RequestsModule : BaseModule
|
public class RequestsModule : BaseModule
|
||||||
{
|
{
|
||||||
|
|
||||||
public RequestsModule(IRequestService service, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<PlexSettings> plex, INotificationService notify) : base("requests")
|
public RequestsModule(IRequestService service, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<PlexSettings> plex, INotificationService notify) : base("requests")
|
||||||
{
|
{
|
||||||
Service = service;
|
Service = service;
|
||||||
|
@ -56,6 +56,7 @@ namespace PlexRequests.UI.Modules
|
||||||
Get["/"] = _ => LoadRequests();
|
Get["/"] = _ => LoadRequests();
|
||||||
Get["/movies"] = _ => GetMovies();
|
Get["/movies"] = _ => GetMovies();
|
||||||
Get["/tvshows"] = _ => GetTvShows();
|
Get["/tvshows"] = _ => GetTvShows();
|
||||||
|
Get["/albums"] = _ => GetAlbumRequests();
|
||||||
Post["/delete"] = _ => DeleteRequest((int)Request.Form.id);
|
Post["/delete"] = _ => DeleteRequest((int)Request.Form.id);
|
||||||
Post["/reportissue"] = _ => ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
|
Post["/reportissue"] = _ => ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
|
||||||
Post["/reportissuecomment"] = _ => ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea);
|
Post["/reportissuecomment"] = _ => ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea);
|
||||||
|
@ -79,28 +80,38 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private Response GetMovies()
|
private Response GetMovies()
|
||||||
{
|
{
|
||||||
|
var settings = PrSettings.GetSettings();
|
||||||
var isAdmin = Context.CurrentUser.IsAuthenticated();
|
var isAdmin = Context.CurrentUser.IsAuthenticated();
|
||||||
var dbMovies = Service.GetAll().Where(x => x.Type == RequestType.Movie);
|
var dbMovies = Service.GetAll().Where(x => x.Type == RequestType.Movie);
|
||||||
var viewModel = dbMovies.Select(movie => new RequestViewModel
|
if (settings.UsersCanViewOnlyOwnRequests && !isAdmin)
|
||||||
{
|
{
|
||||||
ProviderId = movie.ProviderId,
|
dbMovies = dbMovies.Where(x => x.UserHasRequested(Username));
|
||||||
Type = movie.Type,
|
}
|
||||||
Status = movie.Status,
|
|
||||||
ImdbId = movie.ImdbId,
|
var viewModel = dbMovies.Select(movie => {
|
||||||
Id = movie.Id,
|
return new RequestViewModel
|
||||||
PosterPath = movie.PosterPath,
|
{
|
||||||
ReleaseDate = movie.ReleaseDate.Humanize(),
|
ProviderId = movie.ProviderId,
|
||||||
RequestedDate = movie.RequestedDate.Humanize(),
|
Type = movie.Type,
|
||||||
Approved = movie.Approved,
|
Status = movie.Status,
|
||||||
Title = movie.Title,
|
ImdbId = movie.ImdbId,
|
||||||
Overview = movie.Overview,
|
Id = movie.Id,
|
||||||
RequestedBy = movie.RequestedBy,
|
PosterPath = movie.PosterPath,
|
||||||
ReleaseYear = movie.ReleaseDate.Year.ToString(),
|
ReleaseDate = movie.ReleaseDate.Humanize(),
|
||||||
Available = movie.Available,
|
ReleaseDateTicks = movie.ReleaseDate.Ticks,
|
||||||
Admin = isAdmin,
|
RequestedDate = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Humanize(),
|
||||||
Issues = movie.Issues.Humanize(LetterCasing.Title),
|
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks,
|
||||||
OtherMessage = movie.OtherMessage,
|
Approved = movie.Available || movie.Approved,
|
||||||
AdminNotes = movie.AdminNote
|
Title = movie.Title,
|
||||||
|
Overview = movie.Overview,
|
||||||
|
RequestedUsers = isAdmin ? movie.AllUsers.ToArray() : new string[] { },
|
||||||
|
ReleaseYear = movie.ReleaseDate.Year.ToString(),
|
||||||
|
Available = movie.Available,
|
||||||
|
Admin = isAdmin,
|
||||||
|
Issues = movie.Issues.Humanize(LetterCasing.Title),
|
||||||
|
OtherMessage = movie.OtherMessage,
|
||||||
|
AdminNotes = movie.AdminNote,
|
||||||
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
return Response.AsJson(viewModel);
|
return Response.AsJson(viewModel);
|
||||||
|
@ -108,29 +119,80 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private Response GetTvShows()
|
private Response GetTvShows()
|
||||||
{
|
{
|
||||||
|
var settings = PrSettings.GetSettings();
|
||||||
var isAdmin = Context.CurrentUser.IsAuthenticated();
|
var isAdmin = Context.CurrentUser.IsAuthenticated();
|
||||||
var dbTv = Service.GetAll().Where(x => x.Type == RequestType.TvShow);
|
var dbTv = Service.GetAll().Where(x => x.Type == RequestType.TvShow);
|
||||||
var viewModel = dbTv.Select(tv => new RequestViewModel
|
if (settings.UsersCanViewOnlyOwnRequests && !isAdmin)
|
||||||
{
|
{
|
||||||
ProviderId = tv.ProviderId,
|
dbTv = dbTv.Where(x => x.UserHasRequested(Username));
|
||||||
Type = tv.Type,
|
}
|
||||||
Status = tv.Status,
|
|
||||||
ImdbId = tv.ImdbId,
|
var viewModel = dbTv.Select(tv => {
|
||||||
Id = tv.Id,
|
return new RequestViewModel
|
||||||
PosterPath = tv.PosterPath,
|
{
|
||||||
ReleaseDate = tv.ReleaseDate.Humanize(),
|
ProviderId = tv.ProviderId,
|
||||||
RequestedDate = tv.RequestedDate.Humanize(),
|
Type = tv.Type,
|
||||||
Approved = tv.Approved,
|
Status = tv.Status,
|
||||||
Title = tv.Title,
|
ImdbId = tv.ImdbId,
|
||||||
Overview = tv.Overview,
|
Id = tv.Id,
|
||||||
RequestedBy = tv.RequestedBy,
|
PosterPath = tv.PosterPath,
|
||||||
ReleaseYear = tv.ReleaseDate.Year.ToString(),
|
ReleaseDate = tv.ReleaseDate.Humanize(),
|
||||||
Available = tv.Available,
|
ReleaseDateTicks = tv.ReleaseDate.Ticks,
|
||||||
Admin = isAdmin,
|
RequestedDate = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Humanize(),
|
||||||
Issues = tv.Issues.Humanize(LetterCasing.Title),
|
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Ticks,
|
||||||
OtherMessage = tv.OtherMessage,
|
Approved = tv.Available || tv.Approved,
|
||||||
AdminNotes = tv.AdminNote,
|
Title = tv.Title,
|
||||||
TvSeriesRequestType = tv.SeasonsRequested
|
Overview = tv.Overview,
|
||||||
|
RequestedUsers = isAdmin ? tv.AllUsers.ToArray() : new string[] { },
|
||||||
|
ReleaseYear = tv.ReleaseDate.Year.ToString(),
|
||||||
|
Available = tv.Available,
|
||||||
|
Admin = isAdmin,
|
||||||
|
Issues = tv.Issues.Humanize(LetterCasing.Title),
|
||||||
|
OtherMessage = tv.OtherMessage,
|
||||||
|
AdminNotes = tv.AdminNote,
|
||||||
|
TvSeriesRequestType = tv.SeasonsRequested
|
||||||
|
};
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return Response.AsJson(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response GetAlbumRequests()
|
||||||
|
{
|
||||||
|
var settings = PrSettings.GetSettings();
|
||||||
|
var isAdmin = Context.CurrentUser.IsAuthenticated();
|
||||||
|
var dbAlbum = Service.GetAll().Where(x => x.Type == RequestType.Album);
|
||||||
|
if (settings.UsersCanViewOnlyOwnRequests && !isAdmin)
|
||||||
|
{
|
||||||
|
dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username));
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewModel = dbAlbum.Select(album => {
|
||||||
|
return new RequestViewModel
|
||||||
|
{
|
||||||
|
ProviderId = album.ProviderId,
|
||||||
|
Type = album.Type,
|
||||||
|
Status = album.Status,
|
||||||
|
ImdbId = album.ImdbId,
|
||||||
|
Id = album.Id,
|
||||||
|
PosterPath = album.PosterPath,
|
||||||
|
ReleaseDate = album.ReleaseDate.Humanize(),
|
||||||
|
ReleaseDateTicks = album.ReleaseDate.Ticks,
|
||||||
|
RequestedDate = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Humanize(),
|
||||||
|
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Ticks,
|
||||||
|
Approved = album.Available || album.Approved,
|
||||||
|
Title = album.Title,
|
||||||
|
Overview = album.Overview,
|
||||||
|
RequestedUsers = isAdmin ? album.AllUsers.ToArray() : new string[] { },
|
||||||
|
ReleaseYear = album.ReleaseDate.Year.ToString(),
|
||||||
|
Available = album.Available,
|
||||||
|
Admin = isAdmin,
|
||||||
|
Issues = album.Issues.Humanize(LetterCasing.Title),
|
||||||
|
OtherMessage = album.OtherMessage,
|
||||||
|
AdminNotes = album.AdminNote,
|
||||||
|
TvSeriesRequestType = album.SeasonsRequested,
|
||||||
|
MusicBrainzId = album.MusicBrainzId
|
||||||
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
return Response.AsJson(viewModel);
|
return Response.AsJson(viewModel);
|
||||||
|
@ -165,7 +227,7 @@ namespace PlexRequests.UI.Modules
|
||||||
}
|
}
|
||||||
originalRequest.Issues = issue;
|
originalRequest.Issues = issue;
|
||||||
originalRequest.OtherMessage = !string.IsNullOrEmpty(comment)
|
originalRequest.OtherMessage = !string.IsNullOrEmpty(comment)
|
||||||
? $"{Session[SessionKeys.UsernameKey]} - {comment}"
|
? $"{Username} - {comment}"
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
@ -173,7 +235,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
var model = new NotificationModel
|
var model = new NotificationModel
|
||||||
{
|
{
|
||||||
User = Session[SessionKeys.UsernameKey].ToString(),
|
User = Username,
|
||||||
NotificationType = NotificationType.Issue,
|
NotificationType = NotificationType.Issue,
|
||||||
Title = originalRequest.Title,
|
Title = originalRequest.Title,
|
||||||
DateTime = DateTime.Now,
|
DateTime = DateTime.Now,
|
||||||
|
|
|
@ -36,6 +36,7 @@ using NLog;
|
||||||
|
|
||||||
using PlexRequests.Api;
|
using PlexRequests.Api;
|
||||||
using PlexRequests.Api.Interfaces;
|
using PlexRequests.Api.Interfaces;
|
||||||
|
using PlexRequests.Api.Models.Music;
|
||||||
using PlexRequests.Core;
|
using PlexRequests.Core;
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
|
@ -54,7 +55,7 @@ namespace PlexRequests.UI.Modules
|
||||||
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,
|
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,
|
||||||
IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings,
|
IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings,
|
||||||
ISettingsService<SickRageSettings> sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi,
|
ISettingsService<SickRageSettings> sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi,
|
||||||
INotificationService notify) : base("search")
|
INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService<HeadphonesSettings> hpService) : base("search")
|
||||||
{
|
{
|
||||||
CpService = cpSettings;
|
CpService = cpSettings;
|
||||||
PrService = prSettings;
|
PrService = prSettings;
|
||||||
|
@ -69,17 +70,23 @@ namespace PlexRequests.UI.Modules
|
||||||
SickRageService = sickRageService;
|
SickRageService = sickRageService;
|
||||||
SickrageApi = srApi;
|
SickrageApi = srApi;
|
||||||
NotificationService = notify;
|
NotificationService = notify;
|
||||||
|
MusicBrainzApi = mbApi;
|
||||||
|
HeadphonesApi = hpApi;
|
||||||
|
HeadphonesService = hpService;
|
||||||
|
|
||||||
|
|
||||||
Get["/"] = parameters => RequestLoad();
|
Get["/"] = parameters => RequestLoad();
|
||||||
|
|
||||||
Get["movie/{searchTerm}"] = parameters => SearchMovie((string)parameters.searchTerm);
|
Get["movie/{searchTerm}"] = parameters => SearchMovie((string)parameters.searchTerm);
|
||||||
Get["tv/{searchTerm}"] = parameters => SearchTvShow((string)parameters.searchTerm);
|
Get["tv/{searchTerm}"] = parameters => SearchTvShow((string)parameters.searchTerm);
|
||||||
|
Get["music/{searchTerm}"] = parameters => SearchMusic((string)parameters.searchTerm);
|
||||||
|
|
||||||
Get["movie/upcoming"] = parameters => UpcomingMovies();
|
Get["movie/upcoming"] = parameters => UpcomingMovies();
|
||||||
Get["movie/playing"] = parameters => CurrentlyPlayingMovies();
|
Get["movie/playing"] = parameters => CurrentlyPlayingMovies();
|
||||||
|
|
||||||
Post["request/movie"] = parameters => RequestMovie((int)Request.Form.movieId);
|
Post["request/movie"] = parameters => RequestMovie((int)Request.Form.movieId);
|
||||||
Post["request/tv"] = parameters => RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons);
|
Post["request/tv"] = parameters => RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons);
|
||||||
|
Post["request/album"] = parameters => RequestAlbum((string)Request.Form.albumId);
|
||||||
}
|
}
|
||||||
private TheMovieDbApi MovieApi { get; }
|
private TheMovieDbApi MovieApi { get; }
|
||||||
private INotificationService NotificationService { get; }
|
private INotificationService NotificationService { get; }
|
||||||
|
@ -93,9 +100,11 @@ namespace PlexRequests.UI.Modules
|
||||||
private ISettingsService<PlexRequestSettings> PrService { get; }
|
private ISettingsService<PlexRequestSettings> PrService { get; }
|
||||||
private ISettingsService<SonarrSettings> SonarrService { get; }
|
private ISettingsService<SonarrSettings> SonarrService { get; }
|
||||||
private ISettingsService<SickRageSettings> SickRageService { get; }
|
private ISettingsService<SickRageSettings> SickRageService { get; }
|
||||||
|
private ISettingsService<HeadphonesSettings> HeadphonesService { get; }
|
||||||
private IAvailabilityChecker Checker { get; }
|
private IAvailabilityChecker Checker { get; }
|
||||||
|
private IMusicBrainzApi MusicBrainzApi { get; }
|
||||||
|
private IHeadphonesApi HeadphonesApi { get; }
|
||||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
private string AuthToken => Cache.GetOrSet(CacheKeys.TvDbToken, TvApi.Authenticate, 50);
|
|
||||||
|
|
||||||
private Negotiator RequestLoad()
|
private Negotiator RequestLoad()
|
||||||
{
|
{
|
||||||
|
@ -152,6 +161,30 @@ namespace PlexRequests.UI.Modules
|
||||||
return Response.AsJson(model);
|
return Response.AsJson(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response SearchMusic(string searchTerm)
|
||||||
|
{
|
||||||
|
var albums = MusicBrainzApi.SearchAlbum(searchTerm);
|
||||||
|
var releases = albums.releases ?? new List<Release>();
|
||||||
|
var model = new List<SearchMusicViewModel>();
|
||||||
|
foreach (var a in releases)
|
||||||
|
{
|
||||||
|
var img = GetMusicBrainzCoverArt(a.id);
|
||||||
|
model.Add(new SearchMusicViewModel
|
||||||
|
{
|
||||||
|
Title = a.title,
|
||||||
|
Id = a.id,
|
||||||
|
Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(),
|
||||||
|
Overview = a.disambiguation,
|
||||||
|
ReleaseDate = a.date,
|
||||||
|
TrackCount = a.TrackCount,
|
||||||
|
CoverArtUrl = img,
|
||||||
|
ReleaseType = a.status,
|
||||||
|
Country = a.country
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Response.AsJson(model);
|
||||||
|
}
|
||||||
|
|
||||||
private Response UpcomingMovies() // TODO : Not used
|
private Response UpcomingMovies() // TODO : Not used
|
||||||
{
|
{
|
||||||
var movies = MovieApi.GetUpcomingMovies();
|
var movies = MovieApi.GetUpcomingMovies();
|
||||||
|
@ -174,16 +207,26 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
var movieApi = new TheMovieDbApi();
|
var movieApi = new TheMovieDbApi();
|
||||||
var movieInfo = movieApi.GetMovieInformation(movieId).Result;
|
var movieInfo = movieApi.GetMovieInformation(movieId).Result;
|
||||||
string fullMovieName = string.Format("{0}{1}", 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");
|
||||||
Log.Trace(movieInfo.DumpJson);
|
Log.Trace(movieInfo.DumpJson);
|
||||||
//#if !DEBUG
|
//#if !DEBUG
|
||||||
|
|
||||||
|
var settings = PrService.GetSettings();
|
||||||
|
|
||||||
|
// check if the movie has already been requested
|
||||||
Log.Info("Requesting movie with id {0}", movieId);
|
Log.Info("Requesting movie with id {0}", movieId);
|
||||||
if (RequestService.CheckRequest(movieId))
|
var existingRequest = RequestService.CheckRequest(movieId);
|
||||||
|
if (existingRequest != null)
|
||||||
{
|
{
|
||||||
Log.Trace("movie with id {0} exists", movieId);
|
// check if the current user is already marked as a requester for this movie, if not, add them
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} has already been requested!" });
|
if (!existingRequest.UserHasRequested(Username))
|
||||||
|
{
|
||||||
|
existingRequest.RequestedUsers.Add(Username);
|
||||||
|
RequestService.UpdateRequest(existingRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" });
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Debug("movie with id {0} doesnt exists", movieId);
|
Log.Debug("movie with id {0} doesnt exists", movieId);
|
||||||
|
@ -211,16 +254,14 @@ namespace PlexRequests.UI.Modules
|
||||||
Title = movieInfo.Title,
|
Title = movieInfo.Title,
|
||||||
ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue,
|
ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue,
|
||||||
Status = movieInfo.Status,
|
Status = movieInfo.Status,
|
||||||
RequestedDate = DateTime.Now,
|
RequestedDate = DateTime.UtcNow,
|
||||||
Approved = false,
|
Approved = false,
|
||||||
RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
|
RequestedUsers = new List<string>() { Username },
|
||||||
Issues = IssueState.None,
|
Issues = IssueState.None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var settings = PrService.GetSettings();
|
|
||||||
Log.Trace(settings.DumpJson());
|
Log.Trace(settings.DumpJson());
|
||||||
if (!settings.RequireMovieApproval)
|
if (!settings.RequireMovieApproval || settings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
var cpSettings = CpService.GetSettings();
|
var cpSettings = CpService.GetSettings();
|
||||||
|
|
||||||
|
@ -247,7 +288,7 @@ namespace PlexRequests.UI.Modules
|
||||||
};
|
};
|
||||||
NotificationService.Publish(notificationModel);
|
NotificationService.Publish(notificationModel);
|
||||||
|
|
||||||
return Response.AsJson(new JsonResponseModel {Result = true});
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
Response.AsJson(new JsonResponseModel
|
Response.AsJson(new JsonResponseModel
|
||||||
|
@ -272,7 +313,7 @@ namespace PlexRequests.UI.Modules
|
||||||
};
|
};
|
||||||
NotificationService.Publish(notificationModel);
|
NotificationService.Publish(notificationModel);
|
||||||
|
|
||||||
return Response.AsJson(new JsonResponseModel { Result = true });
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,9 +351,20 @@ namespace PlexRequests.UI.Modules
|
||||||
string fullShowName = $"{showInfo.name} ({firstAir.Year})";
|
string fullShowName = $"{showInfo.name} ({firstAir.Year})";
|
||||||
//#if !DEBUG
|
//#if !DEBUG
|
||||||
|
|
||||||
if (RequestService.CheckRequest(showId))
|
var settings = PrService.GetSettings();
|
||||||
|
|
||||||
|
// check if the show has already been requested
|
||||||
|
Log.Info("Requesting tv show with id {0}", showId);
|
||||||
|
var existingRequest = RequestService.CheckRequest(showId);
|
||||||
|
if (existingRequest != null)
|
||||||
{
|
{
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} has already been requested!" });
|
// check if the current user is already marked as a requester for this show, if not, add them
|
||||||
|
if (!existingRequest.UserHasRequested(Username))
|
||||||
|
{
|
||||||
|
existingRequest.RequestedUsers.Add(Username);
|
||||||
|
RequestService.UpdateRequest(existingRequest);
|
||||||
|
}
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" });
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -328,7 +380,7 @@ namespace PlexRequests.UI.Modules
|
||||||
}
|
}
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
|
||||||
var model = new RequestedModel
|
var model = new RequestedModel
|
||||||
{
|
{
|
||||||
ProviderId = showInfo.externals?.thetvdb ?? 0,
|
ProviderId = showInfo.externals?.thetvdb ?? 0,
|
||||||
|
@ -338,9 +390,9 @@ namespace PlexRequests.UI.Modules
|
||||||
Title = showInfo.name,
|
Title = showInfo.name,
|
||||||
ReleaseDate = firstAir,
|
ReleaseDate = firstAir,
|
||||||
Status = showInfo.status,
|
Status = showInfo.status,
|
||||||
RequestedDate = DateTime.Now,
|
RequestedDate = DateTime.UtcNow,
|
||||||
Approved = false,
|
Approved = false,
|
||||||
RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
|
RequestedUsers = new List<string>() { Username },
|
||||||
Issues = IssueState.None,
|
Issues = IssueState.None,
|
||||||
ImdbId = showInfo.externals?.imdb ?? string.Empty,
|
ImdbId = showInfo.externals?.imdb ?? string.Empty,
|
||||||
SeasonCount = showInfo.seasonCount
|
SeasonCount = showInfo.seasonCount
|
||||||
|
@ -363,26 +415,26 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
model.SeasonList = seasonsList.ToArray();
|
model.SeasonList = seasonsList.ToArray();
|
||||||
|
|
||||||
var settings = PrService.GetSettings();
|
if (!settings.RequireTvShowApproval || settings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase)))
|
||||||
if (!settings.RequireTvShowApproval)
|
|
||||||
{
|
{
|
||||||
var sonarrSettings = SonarrService.GetSettings();
|
var sonarrSettings = SonarrService.GetSettings();
|
||||||
var sender = new TvSender(SonarrApi, SickrageApi);
|
var sender = new TvSender(SonarrApi, SickrageApi);
|
||||||
if (sonarrSettings.Enabled)
|
if (sonarrSettings.Enabled)
|
||||||
{
|
{
|
||||||
var result = sender.SendToSonarr(sonarrSettings, model);
|
var result = sender.SendToSonarr(sonarrSettings, model);
|
||||||
if (result != null)
|
if (result != null && !string.IsNullOrEmpty(result.title))
|
||||||
{
|
{
|
||||||
model.Approved = true;
|
model.Approved = true;
|
||||||
Log.Debug("Adding tv to database requests (No approval required & Sonarr)");
|
Log.Debug("Adding tv to database requests (No approval required & Sonarr)");
|
||||||
RequestService.AddRequest(model);
|
RequestService.AddRequest(model);
|
||||||
|
var notify1 = new NotificationModel { Title = model.Title, User = model.RequestedBy, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest };
|
||||||
|
NotificationService.Publish(notify1);
|
||||||
|
|
||||||
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
|
||||||
}
|
}
|
||||||
var notify1 = new NotificationModel { Title = model.Title, User = model.RequestedBy, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest };
|
|
||||||
NotificationService.Publish(notify1);
|
|
||||||
|
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to Sonarr! Please check your settings." });
|
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.ErrorMessage ?? "Something went wrong adding the movie to Sonarr! Please check your settings." });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,5 +473,111 @@ namespace PlexRequests.UI.Modules
|
||||||
var result = Checker.IsAvailable(title, year);
|
var result = Checker.IsAvailable(title, year);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response RequestAlbum(string releaseId)
|
||||||
|
{
|
||||||
|
var settings = PrService.GetSettings();
|
||||||
|
var existingRequest = RequestService.CheckRequest(releaseId);
|
||||||
|
Log.Debug("Checking for an existing request");
|
||||||
|
|
||||||
|
if (existingRequest != null)
|
||||||
|
{
|
||||||
|
Log.Debug("We do have an existing album request");
|
||||||
|
if (!existingRequest.UserHasRequested(Username))
|
||||||
|
{
|
||||||
|
Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username);
|
||||||
|
existingRequest.RequestedUsers.Add(Username);
|
||||||
|
RequestService.UpdateRequest(existingRequest);
|
||||||
|
}
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Log.Debug("This is a new request");
|
||||||
|
|
||||||
|
var albumInfo = MusicBrainzApi.GetAlbum(releaseId);
|
||||||
|
var img = GetMusicBrainzCoverArt(albumInfo.id);
|
||||||
|
|
||||||
|
Log.Trace("Album Details:");
|
||||||
|
Log.Trace(albumInfo.DumpJson());
|
||||||
|
Log.Trace("CoverArt Details:");
|
||||||
|
Log.Trace(img.DumpJson());
|
||||||
|
|
||||||
|
var model = new RequestedModel
|
||||||
|
{
|
||||||
|
Title = albumInfo.title,
|
||||||
|
MusicBrainzId = albumInfo.id,
|
||||||
|
Overview = albumInfo.disambiguation,
|
||||||
|
PosterPath = img,
|
||||||
|
Type = RequestType.Album,
|
||||||
|
ProviderId = 0,
|
||||||
|
RequestedUsers = new List<string>() { Username },
|
||||||
|
Status = albumInfo.status,
|
||||||
|
Issues = IssueState.None
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (!settings.RequireMusicApproval ||
|
||||||
|
settings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
Log.Debug("We don't require approval OR the user is in the whitelist");
|
||||||
|
var hpSettings = HeadphonesService.GetSettings();
|
||||||
|
|
||||||
|
Log.Trace("Headphone Settings:");
|
||||||
|
Log.Trace(hpSettings.DumpJson());
|
||||||
|
|
||||||
|
if (!hpSettings.Enabled)
|
||||||
|
{
|
||||||
|
RequestService.AddRequest(model);
|
||||||
|
return
|
||||||
|
Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = true,
|
||||||
|
Message = $"{model.Title} was successfully added!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var headphonesResult = HeadphonesApi.AddAlbum(hpSettings.ApiKey, hpSettings.FullUri, model.MusicBrainzId);
|
||||||
|
Log.Info("Result from adding album to Headphones = {0}", headphonesResult);
|
||||||
|
RequestService.AddRequest(model);
|
||||||
|
if (headphonesResult)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = true,
|
||||||
|
Message = $"{model.Title} was successfully added!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = false,
|
||||||
|
Message = $"There was a problem adding {model.Title}. Please contact your admin!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = RequestService.AddRequest(model);
|
||||||
|
return Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = true,
|
||||||
|
Message = $"{model.Title} was successfully added!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetMusicBrainzCoverArt(string id)
|
||||||
|
{
|
||||||
|
var coverArt = MusicBrainzApi.GetCoverArt(id);
|
||||||
|
var firstImage = coverArt?.images?.FirstOrDefault();
|
||||||
|
var img = string.Empty;
|
||||||
|
|
||||||
|
if (firstImage != null)
|
||||||
|
{
|
||||||
|
img = firstImage.thumbnails?.small ?? firstImage.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -68,6 +68,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private Response LoginUser()
|
private Response LoginUser()
|
||||||
{
|
{
|
||||||
|
var dateTimeOffset = Request.Form.DateTimeOffset;
|
||||||
var username = Request.Form.username.Value;
|
var username = Request.Form.username.Value;
|
||||||
Log.Debug("Username \"{0}\" attempting to login",username);
|
Log.Debug("Username \"{0}\" attempting to login",username);
|
||||||
if (string.IsNullOrWhiteSpace(username))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
|
@ -138,6 +139,8 @@ namespace PlexRequests.UI.Modules
|
||||||
Session[SessionKeys.UsernameKey] = (string)username;
|
Session[SessionKeys.UsernameKey] = (string)username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset;
|
||||||
|
|
||||||
return Response.AsJson(authenticated
|
return Response.AsJson(authenticated
|
||||||
? new JsonResponseModel { Result = true }
|
? new JsonResponseModel { Result = true }
|
||||||
: new JsonResponseModel { Result = false, Message = "Incorrect User or Password"});
|
: new JsonResponseModel { Result = false, Message = "Incorrect User or Password"});
|
||||||
|
@ -170,7 +173,7 @@ namespace PlexRequests.UI.Modules
|
||||||
var users = Api.GetUsers(authToken);
|
var users = Api.GetUsers(authToken);
|
||||||
Log.Debug("Plex Users: ");
|
Log.Debug("Plex Users: ");
|
||||||
Log.Debug(users.DumpJson());
|
Log.Debug(users.DumpJson());
|
||||||
var allUsers = users.User?.Where(x => !string.IsNullOrEmpty(x.Username));
|
var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Username));
|
||||||
return allUsers != null && allUsers.Any(x => x.Username.Equals(username, StringComparison.CurrentCultureIgnoreCase));
|
return allUsers != null && allUsers.Any(x => x.Username.Equals(username, StringComparison.CurrentCultureIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,7 @@
|
||||||
<Compile Include="Helpers\TvSender.cs" />
|
<Compile Include="Helpers\TvSender.cs" />
|
||||||
<Compile Include="Helpers\ValidationHelper.cs" />
|
<Compile Include="Helpers\ValidationHelper.cs" />
|
||||||
<Compile Include="Models\DatatablesModel.cs" />
|
<Compile Include="Models\DatatablesModel.cs" />
|
||||||
|
<Compile Include="Models\SearchMusicViewModel.cs" />
|
||||||
<Compile Include="Validators\PushoverSettingsValidator.cs" />
|
<Compile Include="Validators\PushoverSettingsValidator.cs" />
|
||||||
<Compile Include="Validators\PushbulletSettingsValidator.cs" />
|
<Compile Include="Validators\PushbulletSettingsValidator.cs" />
|
||||||
<Compile Include="Validators\EmailNotificationSettingsValidator.cs" />
|
<Compile Include="Validators\EmailNotificationSettingsValidator.cs" />
|
||||||
|
@ -371,6 +372,9 @@
|
||||||
<Content Include="Views\Admin\PushoverNotifications.cshtml">
|
<Content Include="Views\Admin\PushoverNotifications.cshtml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Views\Admin\Headphones.cshtml">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<None Include="Web.Debug.config">
|
<None Include="Web.Debug.config">
|
||||||
<DependentUpon>web.config</DependentUpon>
|
<DependentUpon>web.config</DependentUpon>
|
||||||
</None>
|
</None>
|
||||||
|
|
|
@ -88,6 +88,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button id="testEmail" type="submit" class="btn btn-primary-outline">Test</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div>
|
||||||
|
@ -128,7 +133,32 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#testEmail').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var port = $('#EmailPort').val();
|
||||||
|
if (isNaN(port)) {
|
||||||
|
generateNotify("You must specify a valid Port.", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var $form = $("#mainForm");
|
||||||
|
$.ajax({
|
||||||
|
type: $form.prop("method"),
|
||||||
|
data: $form.serialize(),
|
||||||
|
url: '/admin/testemailnotification',
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
162
PlexRequests.UI/Views/Admin/Headphones.cshtml
Normal file
162
PlexRequests.UI/Views/Admin/Headphones.cshtml
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
@Html.Partial("_Sidebar")
|
||||||
|
@{
|
||||||
|
int port;
|
||||||
|
if (Model.Port == 0)
|
||||||
|
{
|
||||||
|
port = 8081;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
port = Model.Port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<div class="col-sm-8 col-sm-push-1">
|
||||||
|
<form class="form-horizontal" method="POST" id="mainForm">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Headphones Settings</legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
@if (Model.Enabled)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="Enabled" name="Enabled" checked="checked"><text>Enabled</text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="Enabled" name="Enabled"><text>Enabled</text>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
@if (Model.Ssl)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="Ssl" name="Ssl" checked="checked"><text>SSL</text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="Ssl" name="Ssl"><text>SSL</text>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Ip" class="control-label">Headphones Hostname or IP</label>
|
||||||
|
<div class="">
|
||||||
|
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" value="@Model.Ip">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="portNumber" class="control-label">Port</label>
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="ApiKey" class="control-label">Headphones API Key</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" class="form-control form-control-custom " id="ApiKey" name="ApiKey" value="@Model.ApiKey">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="SubDir" class="control-label">Headphones SubDirectory</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" class="form-control form-control-custom " id="SubDir" name="SubDir" value="@Model.SubDir">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button type="submit" id="getProfiles" class="btn btn-primary-outline">Get Quality Profiles</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button id="testHeadphones" type="submit" class="btn btn-primary-outline">Test Connectivity</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button id="save" type="submit" class="btn btn-primary-outline">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
$('#testHeadphones').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $form = $("#mainForm");
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: $form.prop("method"),
|
||||||
|
url: "/test/headphones",
|
||||||
|
data: $form.serialize(),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (response) {
|
||||||
|
console.log(response);
|
||||||
|
if (response.result === true) {
|
||||||
|
generateNotify(response.message, "success");
|
||||||
|
$('#authToken').val(response.authToken);
|
||||||
|
} else {
|
||||||
|
generateNotify(response.message, "warning");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
console.log(e);
|
||||||
|
generateNotify("Something went wrong!", "danger");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#save').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var port = $('#portNumber').val();
|
||||||
|
if (isNaN(port)) {
|
||||||
|
generateNotify("You must specify a Port.", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var $form = $("#mainForm");
|
||||||
|
var qualityProfile = $("#profiles option:selected").val();
|
||||||
|
var data = $form.serialize();
|
||||||
|
data = data + "&profileId=" + qualityProfile;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: $form.prop("method"),
|
||||||
|
data: data,
|
||||||
|
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>
|
|
@ -36,6 +36,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button id="testPushbullet" type="submit" class="btn btn-primary-outline">Test</button>
|
||||||
|
</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>
|
||||||
|
@ -70,5 +76,28 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#testPushbullet').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $form = $("#mainForm");
|
||||||
|
$.ajax({
|
||||||
|
type: $form.prop("method"),
|
||||||
|
data: $form.serialize(),
|
||||||
|
url: '/admin/testpushbulletnotification',
|
||||||
|
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>
|
|
@ -36,6 +36,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button id="testPushover" type="submit" class="btn btn-primary-outline">Test</button>
|
||||||
|
</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>
|
||||||
|
@ -70,5 +76,28 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#testPushover').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $form = $("#mainForm");
|
||||||
|
$.ajax({
|
||||||
|
type: $form.prop("method"),
|
||||||
|
data: $form.serialize(),
|
||||||
|
url: '/admin/testpushovernotification',
|
||||||
|
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>
|
|
@ -52,6 +52,20 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
@if (Model.SearchForMusic)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="SearchForMusic" name="SearchForMusic" checked="checked"><text>Search for Music</text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="SearchForMusic" name="SearchForMusic"><text>Search for Music</text>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
|
@ -82,6 +96,45 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
@if (Model.RequireMusicApproval)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval" checked="checked"><text>Require approval of Music requests</text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval"><text>Require approval of Music requests</text>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="form-group">A comma separated list of users whose requests do not require approval.</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="noApprovalUsers" class="control-label">Users</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" class="form-control-custom form-control " id="NoApprovalUsers" name="NoApprovalUsers" placeholder="e.g. John, Bobby" value="@Model.NoApprovalUsers">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
@if (Model.UsersCanViewOnlyOwnRequests)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="UsersCanViewOnlyOwnRequests" name="UsersCanViewOnlyOwnRequests" checked="checked"><text>Users can view their own requests only</text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="UsersCanViewOnlyOwnRequests" name="UsersCanViewOnlyOwnRequests"><text>Users can view their own requests only</text>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@*<div class="form-group">
|
@*<div class="form-group">
|
||||||
<label for="WeeklyRequestLimit" class="control-label">Weekly Request Limit</label>
|
<label for="WeeklyRequestLimit" class="control-label">Weekly Request Limit</label>
|
||||||
|
@ -102,4 +155,3 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,14 @@
|
||||||
{
|
{
|
||||||
<a class="list-group-item" href="/admin/sickrage">SickRage</a>
|
<a class="list-group-item" href="/admin/sickrage">SickRage</a>
|
||||||
}
|
}
|
||||||
|
@if (Context.Request.Path == "/admin/headphones")
|
||||||
|
{
|
||||||
|
<a class="list-group-item active" href="/admin/headphones">Headphones</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a class="list-group-item" href="/admin/headphones">Headphones</a>
|
||||||
|
}
|
||||||
|
|
||||||
@if (Context.Request.Path == "/admin/emailnotification")
|
@if (Context.Request.Path == "/admin/emailnotification")
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
Password <input class="form-control form-control-custom" name="Password" type="password"/>
|
Password <input class="form-control form-control-custom" name="Password" type="password"/>
|
||||||
<br/>
|
<br/>
|
||||||
Remember Me <input name="RememberMe" type="checkbox" value="True"/>
|
Remember Me <input name="RememberMe" type="checkbox" value="True"/>
|
||||||
<br/>
|
<br/><br/>
|
||||||
<input class="btn btn-success-outline" type="submit" value="Login"/>
|
<input class="btn btn-success-outline" type="submit" value="Login"/>
|
||||||
|
<input type="hidden" id="DateTimeOffset" name="DateTimeOffset" />
|
||||||
</form>
|
</form>
|
||||||
@if (!Model.AdminExists)
|
@if (!Model.AdminExists)
|
||||||
{
|
{
|
||||||
|
@ -19,3 +20,9 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
var dtOffset = new Date().getTimezoneOffset();
|
||||||
|
$('#DateTimeOffset').val(dtOffset);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
Username <input class="form-control" type="text" name="Username" />
|
Username <input class="form-control form-control-custom" type="text" name="Username" />
|
||||||
<br />
|
<br />
|
||||||
Password <input class="form-control" name="Password" type="password" />
|
Password <input class="form-control form-control-custom" name="Password" type="password" />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<input class="btn btn-success-outline" type="submit" value="Create User" />
|
<input class="btn btn-success-outline" type="submit" value="Create User" />
|
||||||
|
|
|
@ -2,12 +2,8 @@
|
||||||
<div>
|
<div>
|
||||||
<h1>Requests</h1>
|
<h1>Requests</h1>
|
||||||
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4>
|
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4>
|
||||||
@if (Context.CurrentUser.IsAuthenticated())
|
<br />
|
||||||
{
|
|
||||||
<button id="approveAll" class="btn btn-success-outline" type="submit"><i class="fa fa-plus"></i> Approve All</button>
|
|
||||||
<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)
|
||||||
|
@ -18,42 +14,71 @@
|
||||||
{
|
{
|
||||||
<li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab">TV Shows</a></li>
|
<li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab">TV Shows</a></li>
|
||||||
}
|
}
|
||||||
|
@if (Model.SearchForMusic)
|
||||||
|
{
|
||||||
|
<li role="presentation"><a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab">Albums</a></li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
<br />
|
||||||
|
|
||||||
<!-- Tab panes -->
|
<!-- Tab panes -->
|
||||||
<div class="tab-content contentList">
|
<div class="tab-content contentList">
|
||||||
<div class="btn-group col-sm-push-10">
|
<div class="row">
|
||||||
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
<div class="col-sm-12">
|
||||||
Filter
|
<div class="pull-right">
|
||||||
<i class="fa fa-filter"></i>
|
<div class="btn-group">
|
||||||
</a>
|
@if (Context.CurrentUser.IsAuthenticated())
|
||||||
<ul class="dropdown-menu">
|
{
|
||||||
<li><a href="#" class="filter" data-filter="all">All</a></li>
|
@if (Model.SearchForMovies)
|
||||||
<li><a href="#" class="filter" data-filter=".approved-true">Approved</a></li>
|
{
|
||||||
<li><a href="#" class="filter" data-filter=".approved-false">Not Approved</a></li>
|
<button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> Approve Movies</button>
|
||||||
<li><a href="#" class="filter" data-filter=".available-true">Available</a></li>
|
}
|
||||||
<li><a href="#" class="filter" data-filter=".available-false">Not Available</a></li>
|
@if (Model.SearchForTvShows)
|
||||||
</ul>
|
{
|
||||||
</div>
|
<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>
|
||||||
<div class="btn-group col-sm-push-10">
|
}
|
||||||
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
@if (Model.SearchForMusic)
|
||||||
Order
|
{
|
||||||
<i class="fa fa-sort"></i>
|
<button id="approveMusic" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve Music</button>
|
||||||
</a>
|
}
|
||||||
<ul class="dropdown-menu">
|
}
|
||||||
<li><a href="#" class="sort" data-sort="default">Default</a></li>
|
</div>
|
||||||
<li><a href="#" class="sort" data-sort="requestorder:asc">Requested Date</a></li>
|
<div class="btn-group">
|
||||||
</ul>
|
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||||
|
Filter
|
||||||
|
<i class="fa fa-filter"></i>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" class="filter" data-filter="all"><i class="fa fa-check-square"></i> 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-false"><i class="fa fa-square-o"></i> Not Approved</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-false"><i class="fa fa-square-o"></i> Not Available</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||||
|
Order
|
||||||
|
<i class="fa fa-sort"></i>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" class="sort" data-sort="requestorder:desc"><i class="fa fa-check-square"></i> Latest Requests</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="releaseorder:desc"><i class="fa fa-square-o"></i> Latest Releases</a></li>
|
||||||
|
<li><a href="#" class="sort" data-sort="releaseorder:asc"><i class="fa fa-square-o"></i> Oldest Releases</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (Model.SearchForMovies)
|
@if (Model.SearchForMovies)
|
||||||
{
|
{
|
||||||
|
|
||||||
<!-- Movie tab -->
|
<!-- Movie tab -->
|
||||||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
||||||
|
|
||||||
<br/>
|
<br />
|
||||||
<br/>
|
<br />
|
||||||
<!-- Movie content -->
|
<!-- Movie content -->
|
||||||
<div id="movieList">
|
<div id="movieList">
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,24 +86,37 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (Model.SearchForTvShows)
|
@if (Model.SearchForTvShows)
|
||||||
{
|
{
|
||||||
<!-- TV tab -->
|
<!-- TV tab -->
|
||||||
<div role="tabpanel" class="tab-pane" id="TvShowTab">
|
<div role="tabpanel" class="tab-pane" id="TvShowTab">
|
||||||
|
|
||||||
<br/>
|
<br />
|
||||||
<br/>
|
<br />
|
||||||
<!-- TV content -->
|
<!-- TV content -->
|
||||||
<div id="tvList">
|
<div id="tvList">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (Model.SearchForMusic)
|
||||||
|
{
|
||||||
|
<!-- Music tab -->
|
||||||
|
<div role="tabpanel" class="tab-pane" id="MusicTab">
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<!-- TV content -->
|
||||||
|
<div id="MusicList">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script id="search-template" type="text/x-handlebars-template">
|
<script id="search-template" type="text/x-handlebars-template">
|
||||||
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}}">
|
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}}" data-requestorder="{{requestedDateTicks}}" data-releaseorder="{{releaseDateTicks}}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
{{#if_eq type "movie"}}
|
{{#if_eq type "movie"}}
|
||||||
|
@ -122,7 +160,9 @@
|
||||||
{{#if_eq type "tv"}}
|
{{#if_eq type "tv"}}
|
||||||
<div>Series Requested: {{seriesRequested}}</div>
|
<div>Series Requested: {{seriesRequested}}</div>
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
<div>Requested By: {{requestedBy}}</div>
|
{{#if requestedUsers}}
|
||||||
|
<div>Requested By: {{requestedUsers}}</div>
|
||||||
|
{{/if}}
|
||||||
<div>Requested Date: {{requestedDate}}</div>
|
<div>Requested Date: {{requestedDate}}</div>
|
||||||
<div id="issueArea{{requestId}}">
|
<div id="issueArea{{requestId}}">
|
||||||
{{#if otherMessage}}
|
{{#if otherMessage}}
|
||||||
|
@ -181,7 +221,120 @@
|
||||||
<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">Other</a></li>
|
||||||
|
|
||||||
{{#if_eq admin true}}
|
{{#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>
|
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
|
||||||
|
{{/if_eq}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="album-template" type="text/x-handlebars-template">
|
||||||
|
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}}" data-requestorder="{{requestedDateTicks}}" data-releaseorder="{{releaseDateTicks}}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
{{#if coverArtUrl}}
|
||||||
|
<img class="img-responsive" src="{{coverArtUrl}}" width="150" alt="poster">
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-5 ">
|
||||||
|
<div>
|
||||||
|
<a href="https://musicbrainz.org/release/{{id}}" target="_blank">
|
||||||
|
<h4>
|
||||||
|
{{artist}} - {{title}}
|
||||||
|
{{#if year}}
|
||||||
|
({{year}})
|
||||||
|
{{/if}}
|
||||||
|
</h4>
|
||||||
|
</a>
|
||||||
|
<span class="label label-success">{{status}}</span>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div>Release Date: {{releaseDate}}</div>
|
||||||
|
<div>
|
||||||
|
Approved:
|
||||||
|
{{#if_eq approved false}}
|
||||||
|
<i id="{{requestId}}notapproved" class="fa fa-times"></i>
|
||||||
|
{{/if_eq}}
|
||||||
|
{{#if_eq approved true}}
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
{{/if_eq}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Available
|
||||||
|
{{#if_eq available false}}
|
||||||
|
<i id="availableIcon{{requestId}}" class="fa fa-times"></i>
|
||||||
|
{{/if_eq}}
|
||||||
|
{{#if_eq available true}}
|
||||||
|
<i id="availableIcon{{requestId}}" class="fa fa-check"></i>
|
||||||
|
{{/if_eq}}
|
||||||
|
</div>
|
||||||
|
{{#if requestedUsers}}
|
||||||
|
<div>Requested By: {{requestedUsers}}</div>
|
||||||
|
{{/if}}
|
||||||
|
<div>Requested Date: {{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 class="col-sm-2 col-sm-push-3">
|
||||||
|
{{#if_eq admin true}}
|
||||||
|
{{#if_eq approved false}}
|
||||||
|
<form method="POST" action="/approval/approve" id="approve{{requestId}}">
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
{{/if_eq}}
|
||||||
|
<form method="POST" action="/requests/delete" id="delete{{requestId}}">
|
||||||
|
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
|
||||||
|
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> Remove</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST" action="/requests/clearissues" id="clear{{requestId}}">
|
||||||
|
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
|
||||||
|
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-info-outline clear" type="submit"><i class="fa fa-check"></i> Clear Issues</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST" action="/requests/changeavailability" id="change{{requestId}}">
|
||||||
|
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
|
||||||
|
{{#if_eq available true}}
|
||||||
|
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> Mark Unavailable</button>
|
||||||
|
{{else}}
|
||||||
|
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> Mark Available</button>
|
||||||
|
{{/if_eq}}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{/if_eq}}
|
||||||
|
|
||||||
|
<form method="POST" action="/requests/reportissue/" id="report{{requestId}}">
|
||||||
|
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
|
||||||
|
<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">
|
||||||
|
<i class="fa fa-plus"></i> Report Issue
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<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="1" class="dropdownIssue" href="#">No Subtitles</a></li>
|
||||||
|
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li>
|
||||||
|
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li>
|
||||||
|
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li>
|
||||||
|
|
||||||
|
{{#if_eq admin true}}
|
||||||
|
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h1>Search</h1>
|
<h1>Search</h1>
|
||||||
<h4>Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!</h4>
|
<h4>Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!</h4>
|
||||||
|
<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)
|
||||||
|
@ -11,6 +12,10 @@
|
||||||
{
|
{
|
||||||
<li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab">TV Shows</a></li>
|
<li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab">TV Shows</a></li>
|
||||||
}
|
}
|
||||||
|
@if (Model.SearchForMusic)
|
||||||
|
{
|
||||||
|
<li role="presentation"><a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab">Albums</a></li>
|
||||||
|
}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -21,8 +26,18 @@
|
||||||
<!-- Movie tab -->
|
<!-- Movie tab -->
|
||||||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="movieSearchContent" type="text" class="form-control form-control-custom">
|
<input id="movieSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons">
|
||||||
<div class="input-group-addon">
|
<div class="input-group-addon">
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||||
|
Suggestions
|
||||||
|
<i class="fa fa-chevron-down"></i>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a id="moviesComingSoon" href="#">Coming Soon</a></li>
|
||||||
|
<li><a id="moviesInTheaters" href="#">In Theaters</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<i id="movieSearchButton" class="fa fa-search"></i>
|
<i id="movieSearchButton" class="fa fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +55,7 @@
|
||||||
<!-- TV tab -->
|
<!-- TV tab -->
|
||||||
<div role="tabpanel" class="tab-pane" id="TvShowTab">
|
<div role="tabpanel" class="tab-pane" id="TvShowTab">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="tvSearchContent" type="text" class="form-control form-control-custom">
|
<input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search">
|
||||||
<div class="input-group-addon">
|
<div class="input-group-addon">
|
||||||
<i id="tvSearchButton" class="fa fa-search"></i>
|
<i id="tvSearchButton" class="fa fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,12 +67,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (Model.SearchForMusic)
|
||||||
|
{
|
||||||
|
<!-- Music tab -->
|
||||||
|
<div role="tabpanel" class="tab-pane" id="MusicTab">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="musicSearchContent" type="text" class="form-control form-control-custom form-control-search">
|
||||||
|
<div class="input-group-addon">
|
||||||
|
<i id="musicSearchButton" class="fa fa-search"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<!-- Music content -->
|
||||||
|
<div id="musicList">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Movie and TV Results template -->
|
||||||
<script id="search-template" type="text/x-handlebars-template">
|
<script id="search-template" type="text/x-handlebars-template">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
|
@ -90,7 +123,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2 col-sm-push-3">
|
<div class="col-sm-2 col-sm-push-3">
|
||||||
<form method="POST" action="/search/request/{{type}}" id="form{{id}}">
|
<form method="POST" action="/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 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> Request</button>
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
|
@ -102,7 +135,7 @@
|
||||||
</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="#">All Seasons</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="#">First Season</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="#">Latest Season</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -123,4 +156,42 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Music Results template -->
|
||||||
|
<script id="music-template" type="text/x-handlebars-template">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
{{#if coverArtUrl}}
|
||||||
|
<img class="img-responsive" src="{{coverArtUrl}}" width="150" alt="poster">
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-5 ">
|
||||||
|
<div>
|
||||||
|
<a href="https://musicbrainz.org/release/{{id}}" target="_blank">
|
||||||
|
<h4>
|
||||||
|
{{artist}} - {{title}}
|
||||||
|
{{#if year}}
|
||||||
|
({{year}})
|
||||||
|
{{/if}}
|
||||||
|
</h4>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p>{{overview}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 col-sm-push-3">
|
||||||
|
<form method="POST" action="/search/request/{{type}}" id="form{{id}}">
|
||||||
|
<input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" />
|
||||||
|
|
||||||
|
<button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestAlbum" type="submit"><i class="fa fa-plus"></i> Request</button>
|
||||||
|
<br />
|
||||||
|
<small class="row">Track Count: {{trackCount}}</small>
|
||||||
|
<small class="row">Country: {{country}}</small>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<script src="/Content/search.js" type="text/javascript"></script>
|
<script src="/Content/search.js" type="text/javascript"></script>
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
<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">
|
||||||
<link rel="stylesheet" href="~/Content/custom.min.css" type="text/css"/>
|
|
||||||
<link rel="stylesheet" href="~/Content/bootstrap.css" type="text/css"/>
|
<link rel="stylesheet" href="~/Content/bootstrap.css" type="text/css"/>
|
||||||
|
<link rel="stylesheet" href="~/Content/custom.min.css" type="text/css" />
|
||||||
<link rel="stylesheet" href="~/Content/font-awesome.css" type="text/css"/>
|
<link rel="stylesheet" href="~/Content/font-awesome.css" type="text/css"/>
|
||||||
<link rel="stylesheet" href="~/Content/pace.min.css" type="text/css"/>
|
<link rel="stylesheet" href="~/Content/pace.min.css" type="text/css"/>
|
||||||
|
|
||||||
|
@ -87,8 +87,37 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
|
</div>
|
||||||
|
<div class="scroll-top-wrapper ">
|
||||||
|
<span class="scroll-top-inner">
|
||||||
|
<i class="fa fa-2x fa-arrow-circle-up"></i>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
$(document).on('scroll', function () {
|
||||||
|
|
||||||
|
if ($(window).scrollTop() > 100) {
|
||||||
|
$('.scroll-top-wrapper').addClass('show');
|
||||||
|
} else {
|
||||||
|
$('.scroll-top-wrapper').removeClass('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.scroll-top-wrapper').on('click', scrollToTop);
|
||||||
|
});
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
|
verticalOffset = typeof (verticalOffset) != 'undefined' ? verticalOffset : 0;
|
||||||
|
element = $('body');
|
||||||
|
offset = element.offset();
|
||||||
|
offsetTop = offset.top;
|
||||||
|
$('html, body').animate({ scrollTop: offsetTop }, 500, 'linear');
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -38,10 +38,14 @@
|
||||||
$('#loginBtn').click(function (e) {
|
$('#loginBtn').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var $form = $("#loginForm");
|
var $form = $("#loginForm");
|
||||||
|
var formData = $form.serialize();
|
||||||
|
var dtOffset = new Date().getTimezoneOffset();
|
||||||
|
formData += ('&DateTimeOffset=' + dtOffset)
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: $form.prop("method"),
|
type: $form.prop("method"),
|
||||||
url: $form.prop("action"),
|
url: $form.prop("action"),
|
||||||
data: $form.serialize(),
|
data: formData,
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
|
|
@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.travis.yml = .travis.yml
|
.travis.yml = .travis.yml
|
||||||
appveyor.yml = appveyor.yml
|
appveyor.yml = appveyor.yml
|
||||||
|
.github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md
|
||||||
LICENSE = LICENSE
|
LICENSE = LICENSE
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
|
|
44
README.md
44
README.md
|
@ -10,19 +10,19 @@
|
||||||
This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy!
|
This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy!
|
||||||
I wanted to write a similar application in .Net!
|
I wanted to write a similar application in .Net!
|
||||||
|
|
||||||
#Features
|
# Features
|
||||||
|
|
||||||
* Integration with [TheMovieDB](https://www.themoviedb.org/) for all Movies
|
* Movie and TV Show searching, can't find something on Plex? Just request it!
|
||||||
* Integration with [TVMaze](www.tvmaze.com) for all TV shows!
|
* Notifications! Get notified via Email, Pushbullet and Pushover for new requests and issue reports!
|
||||||
* Secure authentication
|
* Send your TV Shows to either [Sonarr](https://sonarr.tv/) or [SickRage](http://www.sickrage.ca/)!
|
||||||
* [Sonarr](https://sonarr.tv/) integration (SickRage/Sickbeard TBD)
|
* Secure authentication so you don't have to worry about those script kiddies
|
||||||
* [CouchPotato](https://couchpota.to/) integration
|
* We check to see if the request is already in Plex, if it's already in Plex then why you requesting it?!
|
||||||
* [SickRage](https://sickrage.github.io/) integration
|
* We have allowed the ability for a user to add a custom note on a request
|
||||||
* Email notifications
|
* It automatically update the status of requests when they are available on Plex
|
||||||
* Pushbullet notifications
|
* Sick, responsive and mobile friendly UI
|
||||||
* Pushover notifications
|
* Headphones integration will be comming soon!
|
||||||
|
|
||||||
#Preview
|
# Preview (Needs updating)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -31,10 +31,10 @@ Download the latest [Release](https://github.com/tidusjar/PlexRequests.Net/relea
|
||||||
Extract the .zip file (Unblock if on Windows! Right Click > Properties > Unblock).
|
Extract the .zip file (Unblock if on Windows! Right Click > Properties > Unblock).
|
||||||
Just run `PlexRequests.exe`! (Mono compatible `mono PlexRequests.exe`)
|
Just run `PlexRequests.exe`! (Mono compatible `mono PlexRequests.exe`)
|
||||||
|
|
||||||
#Configuration
|
# FAQ
|
||||||
|
Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ)
|
||||||
|
|
||||||
To configure PlexRequests you need to register an admin user by clicking on Admin (top left) and press the Register link.
|
# Docker
|
||||||
You will then have a admin menu option once registered where you can setup Sonarr, Couchpotato and any other settings.
|
|
||||||
|
|
||||||
Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile:
|
Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile:
|
||||||
|
|
||||||
|
@ -84,7 +84,9 @@ end script
|
||||||
|
|
||||||
####Reboot, then open up your browser to check that it's running!
|
####Reboot, then open up your browser to check that it's running!
|
||||||
|
|
||||||
```sudo shutdown -r 00```
|
```
|
||||||
|
sudo shutdown -r 00
|
||||||
|
```
|
||||||
|
|
||||||
# Contributors
|
# Contributors
|
||||||
|
|
||||||
|
@ -95,14 +97,6 @@ Please feed free to submit a pull request!
|
||||||
# Donation
|
# Donation
|
||||||
If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet)
|
If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet)
|
||||||
|
|
||||||
###### A massive thanks to everyone below!
|
## A massive thanks to everyone below for all their help!
|
||||||
|
|
||||||
[heartisall](https://github.com/heartisall), [Stuke00](https://github.com/Stuke00), [shiitake](https://github.com/shiitake)
|
[heartisall](https://github.com/heartisall), [Stuke00](https://github.com/Stuke00), [shiitake](https://github.com/shiitake), [Drewster727](https://github.com/Drewster727), Majawat
|
||||||
|
|
||||||
|
|
||||||
# Sponsors
|
|
||||||
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools!!!
|
|
||||||
- [ReSharper](http://www.jetbrains.com/resharper/)
|
|
||||||
- [dotTrace] (https://www.jetbrains.com/profiler/)
|
|
||||||
- [dotMemory] (https://www.jetbrains.com/dotmemory/)
|
|
||||||
- [dotCover] (https://www.jetbrains.com/dotcover/)
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue