The move!

This commit is contained in:
Jamie.Rees 2017-05-16 08:31:44 +01:00
commit 25526cc4d9
1147 changed files with 85 additions and 8524 deletions

16
src/.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
../app/**/*.js
../app/**/*.js.map
../wwwroot/**
# dependencies
../node_modules
../bower_components
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings

View file

@ -0,0 +1,6 @@
;https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/
cd ..
dotnet restore
dotnet publish -c Release /p:AppRuntimeIdentifier=win10-x64
exit

9
src/Build/publish.bat Normal file
View file

@ -0,0 +1,9 @@
;https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/
cd ..
dotnet restore
dotnet publish -c Release /p:AppRuntimeIdentifier=win10-x64
dotnet publish -c Release /p:AppRuntimeIdentifier=osx.10.12-x64
dotnet publish -c Release /p:AppRuntimeIdentifier=ubuntu.16.10-x64
dotnet publish -c Release /p:AppRuntimeIdentifier=debian.8-x64
exit

View file

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Ombi.Api.Emby.Models;
using Ombi.Helpers;
namespace Ombi.Api.Emby
{
public class EmbyApi : IEmbyApi
{
public EmbyApi()
{
Api = new Api();
}
private Api Api { get; }
/// <summary>
/// Returns all users from the Emby Instance
/// </summary>
/// <param name="baseUri"></param>
/// <param name="apiKey"></param>
public async Task<List<EmbyUser>> GetUsers(string baseUri, string apiKey)
{
var request = new Request("emby/users", baseUri, HttpMethod.Get);
AddHeaders(request, apiKey);
var obj = await Api.Request<List<EmbyUser>>(request);
return obj;
}
public async Task<EmbySystemInfo> GetSystemInformation(string apiKey, string baseUrl)
{
var request = new Request("emby/System/Info", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
var obj = await Api.Request<EmbySystemInfo>(request);
return obj;
}
public async Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri)
{
var request = new Request("emby/users/authenticatebyname", baseUri, HttpMethod.Post);
var body = new
{
username,
password = password.GetSha1Hash().ToLower(),
passwordMd5 = password.CalcuateMd5Hash()
};
request.AddJsonBody(body);
request.AddHeader("X-Emby-Authorization",
$"MediaBrowser Client=\"Ombi\", Device=\"Ombi\", DeviceId=\"v3\", Version=\"v3\"");
AddHeaders(request, apiKey);
var obj = await Api.Request<EmbyUser>(request);
return obj;
}
private static void AddHeaders(Request req, string apiKey)
{
if (!string.IsNullOrEmpty(apiKey))
{
req.AddHeader("X-MediaBrowser-Token", apiKey);
}
req.AddHeader("Accept", "application/json");
req.AddContentHeader("Content-Type", "application/json");
req.AddHeader("Device", "Ombi");
}
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Emby.Models;
namespace Ombi.Api.Emby
{
public interface IEmbyApi
{
Task<EmbySystemInfo> GetSystemInformation(string apiKey, string baseUrl);
Task<List<EmbyUser>> GetUsers(string baseUri, string apiKey);
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
}
}

View file

@ -0,0 +1,45 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyConfiguration.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 Ombi.Api.Emby.Models
{
public class EmbyConfiguration
{
public bool PlayDefaultAudioTrack { get; set; }
public bool DisplayMissingEpisodes { get; set; }
public bool DisplayUnairedEpisodes { get; set; }
public object[] GroupedFolders { get; set; }
public string SubtitleMode { get; set; }
public bool DisplayCollectionsView { get; set; }
public bool EnableLocalPassword { get; set; }
public object[] OrderedViews { get; set; }
public object[] LatestItemsExcludes { get; set; }
public bool HidePlayedInLatest { get; set; }
public bool RememberAudioSelections { get; set; }
public bool RememberSubtitleSelections { get; set; }
public bool EnableNextEpisodeAutoPlay { get; set; }
}
}

View file

@ -0,0 +1,59 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyPolicy.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 Ombi.Api.Emby.Models
{
public class EmbyPolicy
{
public bool IsAdministrator { get; set; }
public bool IsHidden { get; set; }
public bool IsDisabled { get; set; }
public object[] BlockedTags { get; set; }
public bool EnableUserPreferenceAccess { get; set; }
public object[] AccessSchedules { get; set; }
public object[] BlockUnratedItems { get; set; }
public bool EnableRemoteControlOfOtherUsers { get; set; }
public bool EnableSharedDeviceControl { get; set; }
public bool EnableLiveTvManagement { get; set; }
public bool EnableLiveTvAccess { get; set; }
public bool EnableMediaPlayback { get; set; }
public bool EnableAudioPlaybackTranscoding { get; set; }
public bool EnableVideoPlaybackTranscoding { get; set; }
public bool EnablePlaybackRemuxing { get; set; }
public bool EnableContentDeletion { get; set; }
public bool EnableContentDownloading { get; set; }
public bool EnableSync { get; set; }
public bool EnableSyncTranscoding { get; set; }
public object[] EnabledDevices { get; set; }
public bool EnableAllDevices { get; set; }
public object[] EnabledChannels { get; set; }
public bool EnableAllChannels { get; set; }
public object[] EnabledFolders { get; set; }
public bool EnableAllFolders { get; set; }
public int InvalidLoginAttemptCount { get; set; }
public bool EnablePublicSharing { get; set; }
}
}

View file

@ -0,0 +1,63 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbySystemInfo.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 Ombi.Api.Emby.Models
{
public class EmbySystemInfo
{
public string SystemUpdateLevel { get; set; }
public string OperatingSystemDisplayName { get; set; }
public bool SupportsRunningAsService { get; set; }
public string MacAddress { get; set; }
public bool HasPendingRestart { get; set; }
public bool SupportsLibraryMonitor { get; set; }
public object[] InProgressInstallations { get; set; }
public int WebSocketPortNumber { get; set; }
public object[] CompletedInstallations { get; set; }
public bool CanSelfRestart { get; set; }
public bool CanSelfUpdate { get; set; }
public object[] FailedPluginAssemblies { get; set; }
public string ProgramDataPath { get; set; }
public string ItemsByNamePath { get; set; }
public string CachePath { get; set; }
public string LogPath { get; set; }
public string InternalMetadataPath { get; set; }
public string TranscodingTempPath { get; set; }
public int HttpServerPortNumber { get; set; }
public bool SupportsHttps { get; set; }
public int HttpsPortNumber { get; set; }
public bool HasUpdateAvailable { get; set; }
public bool SupportsAutoRunAtStartup { get; set; }
public string EncoderLocationType { get; set; }
public string SystemArchitecture { get; set; }
public string LocalAddress { get; set; }
public string WanAddress { get; set; }
public string ServerName { get; set; }
public string Version { get; set; }
public string OperatingSystem { get; set; }
public string Id { get; set; }
}
}

View file

@ -0,0 +1,48 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyUser.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 Ombi.Api.Emby.Models
{
public class EmbyUser
{
public string Name { get; set; }
public string ServerId { get; set; }
public string ConnectUserName { get; set; }
public string ConnectUserId { get; set; }
public string ConnectLinkType { get; set; }
public string Id { get; set; }
public bool HasPassword { get; set; }
public bool HasConfiguredPassword { get; set; }
public bool HasConfiguredEasyPassword { get; set; }
public DateTime LastLoginDate { get; set; }
public DateTime LastActivityDate { get; set; }
public EmbyConfiguration Configuration { get; set; }
public EmbyPolicy Policy { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Api.Emby.Models
{
public class EmbyUserLogin
{
public EmbyUser User { get; set; }
}
}

View file

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status;
namespace Ombi.Api.Plex
{
public interface IPlexApi
{
Task<PlexStatus> GetStatus(string authToken, string uri);
Task<PlexAuthentication> SignIn(UserRequest user);
Task<PlexServer> GetServer(string authToken);
Task<PlexLibraries> GetLibrarySections(string authToken, string plexFullHost);
Task<PlexLibraries> GetLibrary(string authToken, string plexFullHost, string libraryId);
Task<PlexMetadata> GetEpisodeMetaData(string authToken, string host, string ratingKey);
Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, string itemId);
Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, string ratingKey);
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Api.Plex.Models
{
public class Director
{
public int id { get; set; }
public string filter { get; set; }
public string tag { get; set; }
}
}

View file

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Xml.Serialization;
namespace Ombi.Api.Plex.Models
{
public class Directory
{
public Directory()
{
seasons = new List<Directory>();
}
public bool allowSync { get; set; }
public string art { get; set; }
public string composite { get; set; }
public bool filters { get; set; }
public bool refreshing { get; set; }
public string thumb { get; set; }
public string key { get; set; }
public string type { get; set; }
public string title { get; set; }
public string agent { get; set; }
public string scanner { get; set; }
public string language { get; set; }
public string uuid { get; set; }
public int updatedAt { get; set; }
public int createdAt { get; set; }
public Location[] Location { get; set; }
public string providerId { get; set; }
public string guid { get; set; }
public List<Genre> genre { get; set; }
public List<Role> role { get; set; }
public string librarySectionID { get; set; }
public string librarySectionTitle { get; set; }
public string librarySectionUUID { get; set; }
public string personal { get; set; }
public string sourceTitle { get; set; }
public string ratingKey { get; set; }
public string studio { get; set; }
public List<Directory> seasons { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Api.Plex.Models
{
public class Genre
{
public string tag { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Plex.Models
{
public class Location
{
public int id { get; set; }
public string path { get; set; }
}
}

View file

@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace Ombi.Api.Plex.Models
{
public class Mediacontainer
{
public int size { get; set; }
public bool allowSync { get; set; }
public string identifier { get; set; }
public string mediaTagPrefix { get; set; }
public int mediaTagVersion { get; set; }
public string title1 { get; set; }
public List<Directory> Directory { get; set; }
public string art { get; set; }
public int librarySectionID { get; set; }
public string librarySectionTitle { get; set; }
public string librarySectionUUID { get; set; }
public bool nocache { get; set; }
public string thumb { get; set; }
public string title2 { get; set; }
public string viewGroup { get; set; }
public int viewMode { get; set; }
public Metadata[] Metadata { get; set; }
}
}

View file

@ -0,0 +1,21 @@
namespace Ombi.Api.Plex.Models
{
public class Medium
{
public string videoResolution { get; set; }
public int id { get; set; }
public int duration { get; set; }
public int bitrate { get; set; }
public int width { get; set; }
public int height { get; set; }
public float aspectRatio { get; set; }
public int audioChannels { get; set; }
public string audioCodec { get; set; }
public string videoCodec { get; set; }
public string container { get; set; }
public string videoFrameRate { get; set; }
public string audioProfile { get; set; }
public string videoProfile { get; set; }
public Part[] Part { get; set; }
}
}

View file

@ -0,0 +1,50 @@
namespace Ombi.Api.Plex.Models
{
public class Metadata
{
public string ratingKey { get; set; }
public string key { get; set; }
public string studio { get; set; }
public string type { get; set; }
public string title { get; set; }
public string contentRating { get; set; }
public string summary { get; set; }
public int index { get; set; }
public float rating { get; set; }
public int viewCount { get; set; }
public int lastViewedAt { get; set; }
public int year { get; set; }
public string thumb { get; set; }
public string art { get; set; }
public string banner { get; set; }
public string theme { get; set; }
public string duration { get; set; }
public string originallyAvailableAt { get; set; }
public int leafCount { get; set; }
public int viewedLeafCount { get; set; }
public int childCount { get; set; }
public int addedAt { get; set; }
public int updatedAt { get; set; }
public Genre[] Genre { get; set; }
public Role[] Role { get; set; }
public string primaryExtraKey { get; set; }
public string parentRatingKey { get; set; }
public string grandparentRatingKey { get; set; }
public string guid { get; set; }
public int librarySectionID { get; set; }
public string librarySectionKey { get; set; }
public string grandparentKey { get; set; }
public string parentKey { get; set; }
public string grandparentTitle { get; set; }
public string parentTitle { get; set; }
public int parentIndex { get; set; }
public string parentThumb { get; set; }
public string grandparentThumb { get; set; }
public string grandparentArt { get; set; }
public string grandparentTheme { get; set; }
public string chapterSource { get; set; }
public Medium[] Media { get; set; }
public Director[] Director { get; set; }
public Writer[] Writer { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace Ombi.Api.Plex.Models
{
public class Part
{
public int id { get; set; }
public string key { get; set; }
public string duration { get; set; }
public string file { get; set; }
public string size { get; set; }
public string audioProfile { get; set; }
public string container { get; set; }
public string videoProfile { get; set; }
public Stream[] Stream { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using System;
using System.Text;
namespace Ombi.Api.Plex.Models
{
public class PlexAuthentication
{
public User user { get; set; }
}
}

View file

@ -0,0 +1,33 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: Library.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 Ombi.Api.Plex.Models
{
public class PlexLibraries
{
public Mediacontainer MediaContainer { get; set; }
}
}

View file

@ -0,0 +1,33 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: PlexMetadata.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 Ombi.Api.Plex.Models
{
public class PlexMetadata
{
public Mediacontainer MediaContainer { get; set; }
}
}

View file

@ -0,0 +1,33 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: PlexUserRequest.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 Ombi.Api.Plex.Models
{
public class PlexUserRequest
{
public UserRequest user { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Api.Plex.Models
{
public class Role
{
public string tag { get; set; }
}
}

View file

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Ombi.Api.Plex.Models
{
public class Roles
{
public List<object> roles { get; set; }
}
}

View file

@ -0,0 +1,47 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: PlexServer.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Xml.Serialization;
namespace Ombi.Api.Plex.Models.Server
{
[XmlRoot(ElementName = "MediaContainer")]
public class PlexServer
{
[XmlElement(ElementName = "Server")]
public List<ServerInfo> Server { get; set; }
[XmlAttribute(AttributeName = "friendlyName")]
public string FriendlyName { get; set; }
[XmlAttribute(AttributeName = "identifier")]
public string Identifier { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "size")]
public string Size { get; set; }
}
}

View file

@ -0,0 +1,41 @@
using System.Xml.Serialization;
namespace Ombi.Api.Plex.Models.Server
{
[XmlRoot(ElementName = "Server")]
public class ServerInfo
{
[XmlAttribute(AttributeName = "accessToken")]
public string AccessToken { get; set; }
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
[XmlAttribute(AttributeName = "address")]
public string Address { get; set; }
[XmlAttribute(AttributeName = "port")]
public string Port { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
[XmlAttribute(AttributeName = "scheme")]
public string Scheme { get; set; }
[XmlAttribute(AttributeName = "host")]
public string Host { get; set; }
[XmlAttribute(AttributeName = "localAddresses")]
public string LocalAddresses { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "createdAt")]
public string CreatedAt { get; set; }
[XmlAttribute(AttributeName = "updatedAt")]
public string UpdatedAt { get; set; }
[XmlAttribute(AttributeName = "owned")]
public string Owned { get; set; }
[XmlAttribute(AttributeName = "synced")]
public string Synced { get; set; }
[XmlAttribute(AttributeName = "sourceTitle")]
public string SourceTitle { get; set; }
[XmlAttribute(AttributeName = "ownerId")]
public string OwnerId { get; set; }
[XmlAttribute(AttributeName = "home")]
public string Home { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Api.Plex.Models.Status
{
public class Directory
{
public int count { get; set; }
public string key { get; set; }
public string title { get; set; }
}
}

View file

@ -0,0 +1,51 @@
namespace Ombi.Api.Plex.Models.Status
{
public class Mediacontainer
{
public int size { get; set; }
public bool allowCameraUpload { get; set; }
public bool allowChannelAccess { get; set; }
public bool allowMediaDeletion { get; set; }
public bool allowSharing { get; set; }
public bool allowSync { get; set; }
public bool backgroundProcessing { get; set; }
public bool certificate { get; set; }
public bool companionProxy { get; set; }
public string countryCode { get; set; }
public string diagnostics { get; set; }
public bool eventStream { get; set; }
public string friendlyName { get; set; }
public bool hubSearch { get; set; }
public bool itemClusters { get; set; }
public string machineIdentifier { get; set; }
public bool mediaProviders { get; set; }
public bool multiuser { get; set; }
public bool myPlex { get; set; }
public string myPlexMappingState { get; set; }
public string myPlexSigninState { get; set; }
public bool myPlexSubscription { get; set; }
public string myPlexUsername { get; set; }
public bool photoAutoTag { get; set; }
public string platform { get; set; }
public string platformVersion { get; set; }
public bool pluginHost { get; set; }
public bool readOnlyLibraries { get; set; }
public bool requestParametersInCookie { get; set; }
public int streamingBrainVersion { get; set; }
public bool sync { get; set; }
public int transcoderActiveVideoSessions { get; set; }
public bool transcoderAudio { get; set; }
public bool transcoderLyrics { get; set; }
public bool transcoderPhoto { get; set; }
public bool transcoderSubtitles { get; set; }
public bool transcoderVideo { get; set; }
public string transcoderVideoBitrates { get; set; }
public string transcoderVideoQualities { get; set; }
public string transcoderVideoResolutions { get; set; }
public int updatedAt { get; set; }
public bool updater { get; set; }
public string version { get; set; }
public bool voiceSearch { get; set; }
public Directory[] Directory { get; set; }
}
}

View file

@ -0,0 +1,34 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: PlexStatus.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 Ombi.Api.Plex.Models.Status
{
public class PlexStatus
{
public Mediacontainer MediaContainer { get; set; }
}
}

View file

@ -0,0 +1,28 @@
namespace Ombi.Api.Plex.Models
{
public class Stream
{
public int id { get; set; }
public int streamType { get; set; }
public bool _default { get; set; }
public string codec { get; set; }
public int index { get; set; }
public int bitrate { get; set; }
public int bitDepth { get; set; }
public string chromaSubsampling { get; set; }
public float frameRate { get; set; }
public bool hasScalingMatrix { get; set; }
public int height { get; set; }
public int level { get; set; }
public string profile { get; set; }
public int refFrames { get; set; }
public string scanType { get; set; }
public int width { get; set; }
public int channels { get; set; }
public string language { get; set; }
public string languageCode { get; set; }
public string audioChannelLayout { get; set; }
public int samplingRate { get; set; }
public bool selected { get; set; }
}
}

View file

@ -0,0 +1,10 @@
namespace Ombi.Api.Plex.Models
{
public class Subscription
{
public bool active { get; set; }
public string status { get; set; }
public object plan { get; set; }
public object features { get; set; }
}
}

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Ombi.Api.Plex.Models
{
public class User
{
public string email { get; set; }
public string uuid { get; set; }
public string joined_at { get; set; }
public string username { get; set; }
public string title { get; set; }
public string authentication_token { get; set; }
public Subscription subscription { get; set; }
public Roles roles { get; set; }
public List<string> entitlements { get; set; }
public object confirmed_at { get; set; }
public int forum_id { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Plex.Models
{
public class UserRequest
{
public string login { get; set; }
public string password { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Api.Plex.Models
{
public class Writer
{
public int id { get; set; }
public string filter { get; set; }
public string tag { get; set; }
}
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,135 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status;
namespace Ombi.Api.Plex
{
public class PlexApi : IPlexApi
{
public PlexApi()
{
Api = new Api();
}
private Api Api { get; }
private const string SignInUri = "https://plex.tv/users/sign_in.json";
private const string FriendsUri = "https://plex.tv/pms/friends/all";
private const string GetAccountUri = "https://plex.tv/users/account";
private const string ServerUri = "https://plex.tv/pms/servers.xml";
/// <summary>
/// Sign into the Plex API
/// This is for authenticating users credentials with Plex
/// <para>NOTE: Plex "Managed" users do not work</para>
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
public async Task<PlexAuthentication> SignIn(UserRequest user)
{
var userModel = new PlexUserRequest
{
user = user
};
var request = new Request(SignInUri, string.Empty, HttpMethod.Post);
AddHeaders(request);
request.AddJsonBody(userModel);
var obj = await Api.Request<PlexAuthentication>(request);
return obj;
}
public async Task<PlexStatus> GetStatus(string authToken, string uri)
{
var request = new Request(uri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken);
return await Api.Request<PlexStatus>(request);
}
public async Task<PlexServer> GetServer(string authToken)
{
var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken);
return await Api.Request<PlexServer>(request);
}
public async Task<PlexLibraries> GetLibrarySections(string authToken, string plexFullHost)
{
var request = new Request("library/sections", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
return await Api.Request<PlexLibraries>(request);
}
public async Task<PlexLibraries> GetLibrary(string authToken, string plexFullHost, string libraryId)
{
var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
return await Api.Request<PlexLibraries>(request);
}
/// <summary>
// 192.168.1.69:32400/library/metadata/3662/allLeaves
// The metadata ratingkey should be in the Cache
// Search for it and then call the above with the Directory.RatingKey
// THEN! We need the episode metadata using result.Vide.Key ("/library/metadata/3664")
// We then have the GUID which contains the TVDB ID plus the season and episode number: guid="com.plexapp.agents.thetvdb://269586/2/8?lang=en"
/// </summary>
/// <param name="authToken"></param>
/// <param name="plexFullHost"></param>
/// <param name="ratingKey"></param>
/// <returns></returns>
public async Task<PlexMetadata> GetEpisodeMetaData(string authToken, string plexFullHost, string ratingKey)
{
var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
public async Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, string itemId)
{
var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
public async Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, string ratingKey)
{
var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
/// <summary>
/// Adds the required headers and also the authorization header
/// </summary>
/// <param name="request"></param>
/// <param name="authToken"></param>
private void AddHeaders(Request request, string authToken)
{
request.AddHeader("X-Plex-Token", authToken);
AddHeaders(request);
}
/// <summary>
/// Adds the main required headers to the Plex Request
/// </summary>
/// <param name="request"></param>
private void AddHeaders(Request request)
{
request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3");
request.AddHeader("X-Plex-Product", "Ombi");
request.AddHeader("X-Plex-Version", "3");
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
request.AddHeader("Accept", "application/json");
}
}
}

View file

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models;
namespace Ombi.Api.Sonarr
{
public interface ISonarrApi
{
Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl);
Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl);
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Sonarr.Models
{
public class Cutoff
{
public int id { get; set; }
public string name { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Sonarr.Models
{
public class Item
{
public Quality quality { get; set; }
public bool allowed { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Sonarr.Models
{
public class Quality
{
public int id { get; set; }
public string name { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Ombi.Api.Sonarr.Models
{
public class SonarrProfile
{
public string name { get; set; }
public Cutoff cutoff { get; set; }
public List<Item> items { get; set; }
public int id { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Api.Sonarr.Models
{
public class SonarrRootFolder
{
public int id { get; set; }
public string path { get; set; }
public long freespace { get; set; }
}
}

View file

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,37 @@
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models;
namespace Ombi.Api.Sonarr
{
public class SonarrApi : ISonarrApi
{
public SonarrApi()
{
Api = new Api();
}
private Api Api { get; }
public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl)
{
var request = new Request(baseUrl, "/api/profile", HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<SonarrProfile>>(request);
}
public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{
var request = new Request(baseUrl, "/api/rootfolder", HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<SonarrRootFolder>>(request);
}
}
}

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
//using TraktApiSharp.Enums;
//using TraktApiSharp.Objects.Get.Shows;
//using TraktApiSharp.Objects.Get.Shows.Common;
namespace Ombi.Api.Trakt
{
public interface ITraktApi
{
//Task<IEnumerable<TraktMostAnticipatedShow>> GetAnticipatedShows(int? page = default(int?), int? limitPerPage = default(int?));
//Task<IEnumerable<TraktMostWatchedShow>> GetMostWatchesShows(TraktTimePeriod period = null, int? page = default(int?), int? limitPerPage = default(int?));
//Task<IEnumerable<TraktShow>> GetPopularShows(int? page = default(int?), int? limitPerPage = default(int?));
//Task<IEnumerable<TraktTrendingShow>> GetTrendingShows(int? page = default(int?), int? limitPerPage = default(int?));
}
}

View file

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,49 @@
//using System;
//using System.Collections.Generic;
//using System.Threading.Tasks;
//using Ombi.Helpers;
//using TraktApiSharp;
//using TraktApiSharp.Enums;
//using TraktApiSharp.Objects.Get.Shows;
//using TraktApiSharp.Objects.Get.Shows.Common;
//using TraktApiSharp.Requests.Parameters;
//namespace Ombi.Api.Trakt
//{
// public class TraktApi : ITraktApi
// {
// private TraktClient Client { get; }
// private static readonly string Encrypted = "MTM0ZTU2ODM1MGY3NDI3NTExZTI1N2E2NTM0MDI2NjYwNDgwY2Y5YjkzYzc3ZjczNzhmMzQwNjAzYjY3MzgxZA==";
// private readonly string _apiKey = StringCipher.DecryptString(Encrypted, "ApiKey");
// public TraktApi()
// {
// Client = new TraktClient(_apiKey);
// }
// public async Task<IEnumerable<TraktShow>> GetPopularShows(int? page = null, int? limitPerPage = null)
// {
// var popular = await Client.Shows.GetPopularShowsAsync(new TraktExtendedInfo { Full = true }, null, page ?? 1, limitPerPage ?? 10);
// return popular.Value;
// }
// public async Task<IEnumerable<TraktTrendingShow>> GetTrendingShows(int? page = null, int? limitPerPage = null)
// {
// var trendingShowsTop10 = await Client.Shows.GetTrendingShowsAsync(new TraktExtendedInfo { Full = true }, null, page ?? 1, limitPerPage ?? 10);
// return trendingShowsTop10.Value;
// }
// public async Task<IEnumerable<TraktMostAnticipatedShow>> GetAnticipatedShows(int? page = null, int? limitPerPage = null)
// {
// var anticipatedShows = await Client.Shows.GetMostAnticipatedShowsAsync(new TraktExtendedInfo { Full = true }, null, page ?? 1, limitPerPage ?? 10);
// return anticipatedShows.Value;
// }
// public async Task<IEnumerable<TraktMostWatchedShow>> GetMostWatchesShows(TraktTimePeriod period = null, int? page = null, int? limitPerPage = null)
// {
// var anticipatedShows = await Client.Shows.GetMostWatchedShowsAsync(period ?? TraktTimePeriod.Monthly, new TraktExtendedInfo { Full = true }, null, page ?? 1, limitPerPage ?? 10);
// return anticipatedShows.Value;
// }
// }
//}

View file

@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.TvMaze.Models;
namespace Ombi.Api.TvMaze
{
public interface ITvMazeApi
{
Task<IEnumerable<TvMazeEpisodes>> EpisodeLookup(int showId);
Task<List<TvMazeSeasons>> GetSeasons(int id);
Task<List<TvMazeSearch>> Search(string searchTerm);
Task<TvMazeShow> ShowLookup(int showId);
Task<TvMazeShow> ShowLookupByTheTvDbId(int theTvDbId);
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Api.TvMaze.Models
{
public class Country
{
public string code { get; set; }
public string name { get; set; }
public string timezone { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Api.TvMaze.Models
{
public class Externals
{
public string imdb { get; set; }
public int? thetvdb { get; set; }
public int? tvrage { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.TvMaze.Models
{
public class Image
{
public string medium { get; set; }
public string original { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Api.TvMaze.Models
{
public class Links
{
public Nextepisode nextepisode { get; set; }
public Previousepisode previousepisode { get; set; }
public Self self { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Api.TvMaze.Models
{
public class Network
{
public Country country { get; set; }
public int id { get; set; }
public string name { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class Nextepisode
{
public string href { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class Previousepisode
{
public string href { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class Rating
{
public double? average { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Ombi.Api.TvMaze.Models
{
public class Schedule
{
public List<object> days { get; set; }
public string time { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class Self
{
public string href { get; set; }
}
}

View file

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

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.TvMaze.Models
{
public class TvMazeSearch
{
public double score { get; set; }
public Show show { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class TvMazeSeasons : TvMazeShow
{
public int number { get; set; }
}
}

View file

@ -0,0 +1,18 @@
namespace Ombi.Api.TvMaze.Models
{
public class TvMazeEpisodes
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public int season { get; set; }
public int number { get; set; }
public string airdate { get; set; }
public string airtime { get; set; }
public string airstamp { get; set; }
public int runtime { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public Links _links { get; set; }
}
}

View file

@ -0,0 +1,89 @@
using System.Collections.Generic;
namespace Ombi.Api.TvMaze.Models
{
public class TvMazeShow
{
public TvMazeShow()
{
Season = new List<TvMazeCustomSeason>();
}
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public string type { get; set; }
public string language { get; set; }
public List<string> genres { get; set; }
public string status { get; set; }
public double runtime { get; set; }
public string premiered { get; set; }
public Schedule schedule { get; set; }
public Rating rating { get; set; }
public int weight { get; set; }
public Network network { get; set; }
public object webChannel { get; set; }
public Externals externals { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public int updated { get; set; }
public Links _links { get; set; }
public List<TvMazeCustomSeason> Season { get; set; }
public Embedded _embedded { get; set; }
}
public class TvMazeCustomSeason
{
public int SeasonNumber { get; set; }
public List<int> EpisodeNumber { get; set; }
}
public class Season
{
public int id { get; set; }
public string url { get; set; }
public int number { get; set; }
public string name { get; set; }
public int? episodeOrder { get; set; }
public string premiereDate { get; set; }
public string endDate { get; set; }
public Network2 network { get; set; }
public object webChannel { get; set; }
public Image2 image { get; set; }
public string summary { get; set; }
public Links2 _links { get; set; }
}
public class Country2
{
public string name { get; set; }
public string code { get; set; }
public string timezone { get; set; }
}
public class Network2
{
public int id { get; set; }
public string name { get; set; }
public Country2 country { get; set; }
}
public class Image2
{
public string medium { get; set; }
public string original { get; set; }
}
public class Self2
{
public string href { get; set; }
}
public class Links2
{
public Self2 self { get; set; }
}
public class Embedded
{
public List<Season> seasons { get; set; }
}
}

View file

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.Logging.Abstractions">
<HintPath>..\..\..\..\..\.nuget\packages\microsoft.extensions.logging.abstractions\1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View file

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.TvMaze.Models;
using Ombi.Helpers;
namespace Ombi.Api.TvMaze
{
public class TvMazeApi : ITvMazeApi
{
public TvMazeApi(ILogger<TvMazeApi> logger)
{
Api = new Ombi.Api.Api();
Logger = logger;
//Mapper = mapper;
}
private string Uri = "http://api.tvmaze.com";
private Api Api { get; }
private ILogger<TvMazeApi> Logger { get; }
public async Task<List<TvMazeSearch>> Search(string searchTerm)
{
var request = new Request("search/shows", Uri, HttpMethod.Get);
request.AddQueryString("q", searchTerm);
request.ContentHeaders.Add(new KeyValuePair<string, string>("Content-Type","application/json"));
return await Api.Request<List<TvMazeSearch>>(request);
}
public async Task<TvMazeShow> ShowLookup(int showId)
{
var request = new Request($"shows/{showId}", Uri, HttpMethod.Get);
request.AddContentHeader("Content-Type", "application/json");
return await Api.Request<TvMazeShow>(request);
}
public async Task<IEnumerable<TvMazeEpisodes>> EpisodeLookup(int showId)
{
var request = new Request($"shows/{showId}/episodes", Uri, HttpMethod.Get);
request.AddContentHeader("Content-Type", "application/json");
return await Api.Request<List<TvMazeEpisodes>>(request);
}
public async Task<TvMazeShow> ShowLookupByTheTvDbId(int theTvDbId)
{
var request = new Request($"lookup/shows?thetvdb={theTvDbId}", Uri, HttpMethod.Get);
request.AddContentHeader("Content-Type", "application/json");
try
{
var obj = await Api.Request<TvMazeShow>(request);
var episodes = await EpisodeLookup(obj.id);
foreach (var e in episodes)
{
// Check if the season exists
var currentSeason = obj.Season.FirstOrDefault(x => x.SeasonNumber == e.season);
if (currentSeason == null)
{
// Create the season
obj.Season.Add(new TvMazeCustomSeason
{
SeasonNumber = e.season,
EpisodeNumber = new List<int> {e.number}
});
}
else
{
// Just add a new episode into that season
currentSeason.EpisodeNumber.Add(e.number);
}
}
return obj;
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.ApiException, e, "Exception when calling ShowLookupByTheTvDbId with id:{0}",theTvDbId);
return null;
}
}
public async Task<List<TvMazeSeasons>> GetSeasons(int id)
{
var request = new Request($"shows/{id}/seasons", Uri, HttpMethod.Get);
request.AddContentHeader("Content-Type", "application/json");
return await Api.Request<List<TvMazeSeasons>>(request);
}
}
}

65
src/Ombi.Api/Api.cs Normal file
View file

@ -0,0 +1,65 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace Ombi.Api
{
public class Api
{
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
public async Task<T> Request<T>(Request request)
{
using (var httpClient = new HttpClient())
{
using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri))
{
// Add the Json Body
if (request.JsonBody != null)
{
httpRequestMessage.Content = new JsonContent(request.JsonBody);
}
// Add headers
foreach (var header in request.Headers)
{
httpRequestMessage.Headers.Add(header.Key, header.Value);
}
using (var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage))
{
if (!httpResponseMessage.IsSuccessStatusCode)
{
// Logging
}
// do something with the response
var data = httpResponseMessage.Content;
var receivedString = await data.ReadAsStringAsync();
if (request.ContentType == ContentType.Json)
{
return JsonConvert.DeserializeObject<T>(receivedString, Settings);
}
else
{
// XML
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(receivedString);
var value = (T)serializer.Deserialize(reader);
return value;
}
}
}
}
}
}
}

75
src/Ombi.Api/ApiHelper.cs Normal file
View file

@ -0,0 +1,75 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: ApiHelper.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 Ombi.Api
{
public static class ApiHelper
{
public static Uri ChangePath(this Uri uri, string path, params string[] args)
{
var builder = new UriBuilder(uri);
if (args != null && args.Length > 0)
{
builder.Path = builder.Path + string.Format(path, args);
}
else
{
builder.Path += path;
}
return builder.Uri;
}
public static Uri ChangePath(this Uri uri, string path)
{
return ChangePath(uri, path, null);
}
public static Uri AddQueryParameter(this Uri uri, string parameter, string value)
{
if (string.IsNullOrEmpty(parameter) || string.IsNullOrEmpty(value)) return uri;
var builder = new UriBuilder(uri);
var startingTag = string.Empty;
var hasQuery = false;
if (string.IsNullOrEmpty(builder.Query))
{
startingTag = "?";
}
else
{
hasQuery = true;
startingTag = builder.Query.Contains("?") ? "&" : "?";
}
builder.Query = hasQuery
? $"{builder.Query}{startingTag}{parameter}={value}"
: $"{startingTag}{parameter}={value}";
return builder.Uri;
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ombi.Api
{
public enum ContentType
{
Json,
Xml,
Text,
Html,
}
}

View file

@ -0,0 +1,13 @@
using System.Net.Http;
using System.Text;
using Newtonsoft.Json;
namespace Ombi.Api
{
internal class JsonContent : StringContent
{
public JsonContent(object obj) :
base(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json")
{ }
}
}

View file

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
<!--<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup>
</Project>

110
src/Ombi.Api/Request.cs Normal file
View file

@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
namespace Ombi.Api
{
public class Request
{
public Request()
{
}
public Request(string endpoint, string baseUrl, HttpMethod http, ContentType contentType = ContentType.Json)
{
Endpoint = endpoint;
BaseUrl = baseUrl;
HttpMethod = http;
ContentType = contentType;
}
public ContentType ContentType { get; }
public string Endpoint { get; }
public string BaseUrl { get; }
public HttpMethod HttpMethod { get; }
private string FullUrl
{
get
{
var sb = new StringBuilder();
if (!string.IsNullOrEmpty(BaseUrl))
{
sb.Append(!BaseUrl.EndsWith("/") ? string.Format("{0}/", BaseUrl) : BaseUrl);
}
sb.Append(Endpoint.StartsWith("/") ? Endpoint.Remove(0, 1) : Endpoint);
return sb.ToString();
}
}
private Uri _modified;
public Uri FullUri
{
get => _modified != null ? _modified : new Uri(FullUrl);
set => _modified = value;
}
public List<KeyValuePair<string, string>> Headers { get; } = new List<KeyValuePair<string, string>>();
public List<KeyValuePair<string, string>> ContentHeaders { get; } = new List<KeyValuePair<string, string>>();
public object JsonBody { get; set; }
public bool IsValidUrl
{
get
{
try
{
// ReSharper disable once ObjectCreationAsStatement
new Uri(FullUrl);
return true;
}
catch (Exception)
{
return false;
}
}
}
public void AddHeader(string key, string value)
{
Headers.Add(new KeyValuePair<string, string>(key, value));
}
public void AddContentHeader(string key, string value)
{
ContentHeaders.Add(new KeyValuePair<string, string>(key, value));
}
public void AddQueryString(string key, string value)
{
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
var builder = new UriBuilder(FullUri);
var startingTag = string.Empty;
var hasQuery = false;
if (string.IsNullOrEmpty(builder.Query))
{
startingTag = "?";
}
else
{
hasQuery = true;
startingTag = builder.Query.Contains("?") ? "&" : "?";
}
builder.Query = hasQuery
? $"{builder.Query}{startingTag}{key}={value}"
: $"{startingTag}{key}={value}";
_modified = builder.Uri;
}
public void AddJsonBody(object obj)
{
JsonBody = obj;
}
}
}

View file

@ -0,0 +1,36 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: OmbiClaims.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 Ombi.Core.Claims
{
public static class OmbiClaims
{
public const string Admin = nameof(Admin);
public const string AutoApproveMovie = nameof(AutoApproveMovie);
public const string AutoApproveTv = nameof(AutoApproveTv);
public const string PowerUser = nameof(PowerUser);
}
}

View file

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Core.Claims;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Requests;
using Ombi.Core.Requests.Models;
using Ombi.Helpers;
namespace Ombi.Core.Engine
{
public abstract class BaseMediaEngine : BaseEngine
{
protected BaseMediaEngine(IPrincipal identity, IRequestServiceMain requestService) : base(identity)
{
RequestService = requestService;
}
protected IRequestServiceMain RequestService { get; }
protected IRequestService<MovieRequestModel> MovieRequestService => RequestService.MovieRequestService;
protected IRequestService<TvRequestModel> TvRequestService => RequestService.TvRequestService;
private long _cacheTime = 0;
private Dictionary<int, MovieRequestModel> _dbMovies;
private Dictionary<int, TvRequestModel> _dbTv;
protected async Task<Dictionary<int, MovieRequestModel>> GetMovieRequests()
{
long now = DateTime.Now.Ticks;
if (_dbMovies == null || (now - _cacheTime) > 10000)
{
var allResults = await MovieRequestService.GetAllAsync();
var distinctResults = allResults.DistinctBy(x => x.ProviderId);
_dbMovies = distinctResults.ToDictionary(x => x.ProviderId);
_cacheTime = now;
}
return _dbMovies;
}
protected async Task<Dictionary<int, TvRequestModel>> GetTvRequests()
{
long now = DateTime.Now.Ticks;
if (_dbTv == null || (now - _cacheTime) > 10000)
{
var allResults = await TvRequestService.GetAllAsync();
var distinctResults = allResults.DistinctBy(x => x.ProviderId);
_dbTv = distinctResults.ToDictionary(x => x.ProviderId);
_cacheTime = now;
}
return _dbTv;
}
}
}

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
namespace Ombi.Core.Engine
{
public interface ITvRequestEngine
{
Task<IEnumerable<TvRequestModel>> GetTvRequests(int count, int position);
Task RemoveTvRequest(int requestId);
Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv);
Task<IEnumerable<TvRequestModel>> SearchTvRequest(string search);
Task<TvRequestModel> UpdateTvRequest(TvRequestModel request);
}
}

View file

@ -0,0 +1,55 @@
using System.Security.Principal;
using Ombi.Core.Claims;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine.Interfaces
{
public abstract class BaseEngine
{
protected BaseEngine(IPrincipal user)
{
User = user;
}
protected IPrincipal User { get; }
protected string Username => User.Identity.Name;
protected bool HasRole(string roleName)
{
return User.IsInRole(roleName);
}
protected bool ShouldSendNotification(RequestType type)
{
var sendNotification = !ShouldAutoApprove(type); /*|| !prSettings.IgnoreNotifyForAutoApprovedRequests;*/
if (HasRole(OmbiClaims.Admin))
{
sendNotification = false; // Don't bother sending a notification if the user is an admin
}
return sendNotification;
}
public bool ShouldAutoApprove(RequestType requestType)
{
var admin = HasRole(OmbiClaims.Admin);
// if the user is an admin, they go ahead and allow auto-approval
if (admin) return true;
// check by request type if the category requires approval or not
switch (requestType)
{
case RequestType.Movie:
return HasRole(OmbiClaims.AutoApproveMovie);
case RequestType.TvShow:
return HasRole(OmbiClaims.AutoApproveTv);
default:
return false;
}
}
}
}

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
namespace Ombi.Core
{
public interface IMovieEngine
{
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
Task<IEnumerable<SearchMovieViewModel>> Search(string search);
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
Task<IEnumerable<SearchMovieViewModel>> LookupImdbInformation(IEnumerable<SearchMovieViewModel> movies);
}
}

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine
{
public interface IMovieRequestEngine
{
Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model);
bool ShouldAutoApprove(RequestType requestType);
Task<IEnumerable<MovieRequestModel>> GetMovieRequests(int count, int position);
Task<IEnumerable<MovieRequestModel>> SearchMovieRequest(string search);
Task RemoveMovieRequest(int requestId);
Task<MovieRequestModel> UpdateMovieRequest(MovieRequestModel request);
}
}

View file

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
namespace Ombi.Core.Engine.Interfaces
{
public interface ITvSearchEngine
{
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbId);
//Task<IEnumerable<SearchTvShowViewModel>> Popular();
//Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
//Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
//Task<IEnumerable<SearchTvShowViewModel>> Trending();
}
}

View file

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Hangfire;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TvMaze;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Requests.Models;
using Ombi.Store.Entities;
using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Notifications.Models;
namespace Ombi.Core.Engine
{
public class MovieRequestEngine : BaseMediaEngine, IMovieRequestEngine
{
public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, INotificationService notificationService) : base(user, requestService)
{
MovieApi = movieApi;
NotificationService = notificationService;
}
private IMovieDbApi MovieApi { get; }
private INotificationService NotificationService { get; }
public async Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model)
{
var movieInfo = await MovieApi.GetMovieInformation(model.Id);
if (movieInfo == null)
{
return new RequestEngineResult
{
RequestAdded = false,
Message = "There was an issue adding this movie!",
ErrorMessage = $"TheMovieDb didn't have any information for ID {model.Id}"
};
}
var fullMovieName =
$"{movieInfo.Title}{(!string.IsNullOrEmpty(movieInfo.ReleaseDate) ? $" ({DateTime.Parse(movieInfo.ReleaseDate).Year})" : string.Empty)}";
var existingRequest = await MovieRequestService.CheckRequestAsync(model.Id);
if (existingRequest != null)
{
return new RequestEngineResult
{
RequestAdded = false,
Message = $"{fullMovieName} has already been requested"
};
}
// TODO
//try
//{
// var content = PlexContentRepository.GetAll();
// var movies = PlexChecker.GetPlexMovies(content);
// if (PlexChecker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString()))
// {
// return
// Response.AsJson(new JsonResponseModel
// {
// Result = false,
// Message = $"{fullMovieName} is already in Plex!"
// });
// }
//}
//catch (Exception e)
//{
// Log.Error(e);
// return
// Response.AsJson(new JsonResponseModel
// {
// Result = false,
// Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName, GetMediaServerName())
// });
//}
var requestModel = new MovieRequestModel
{
ProviderId = movieInfo.Id,
Type = RequestType.Movie,
Overview = movieInfo.Overview,
ImdbId = movieInfo.ImdbId,
PosterPath = movieInfo.PosterPath,
Title = movieInfo.Title,
ReleaseDate = !string.IsNullOrEmpty(movieInfo.ReleaseDate) ? DateTime.Parse(movieInfo.ReleaseDate) : DateTime.MinValue,
Status = movieInfo.Status,
RequestedDate = DateTime.UtcNow,
Approved = false,
RequestedUsers = new List<string> { Username },
Issues = IssueState.None,
};
try
{
if (ShouldAutoApprove(RequestType.Movie))
{
model.Approved = true;
// var result = await MovieSender.Send(model);
// if (result.Result)
// {
// return await AddRequest(model, settings,
// $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
// }
// if (result.Error)
// {
// return
// Response.AsJson(new JsonResponseModel
// {
// Message = "Could not add movie, please contact your administrator",
// Result = false
// });
// }
// if (!result.MovieSendingEnabled)
// {
// return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
// }
// return Response.AsJson(new JsonResponseModel
// {
// Result = false,
// Message = Resources.UI.Search_CouchPotatoError
// });
}
return await AddMovieRequest(requestModel, /*settings,*/
$"{fullMovieName} has been successfully added!");
}
catch (Exception e)
{
//Log.Fatal(e);
//await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault, e.Message);
var notification = new NotificationModel
{
DateTime = DateTime.Now,
User = Username,
RequestType = RequestType.Movie,
Title = model.Title,
NotificationType = NotificationType.ItemAddedToFaultQueue
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notification).Wait());
//return Response.AsJson(new JsonResponseModel
//{
// Result = true,
// Message = $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"
//});
}
return null;
}
private IEnumerable<EpisodesModel> GetListDifferences(IEnumerable<EpisodesModel> existing, IEnumerable<EpisodesModel> request)
{
var newRequest = request
.Select(r =>
new EpisodesModel
{
SeasonNumber = r.SeasonNumber,
EpisodeNumber = r.EpisodeNumber
}).ToList();
return newRequest.Except(existing);
}
private async Task<RequestEngineResult> AddMovieRequest(MovieRequestModel model, string message)
{
await MovieRequestService.AddRequestAsync(model);
if (ShouldSendNotification(model.Type))
{
var notificationModel = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = model.Type,
ImgSrc = model.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{model.PosterPath}" : model.PosterPath
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel).Wait());
}
//var limit = await RequestLimitRepo.GetAllAsync();
//var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type);
//if (usersLimit == null)
//{
// await RequestLimitRepo.InsertAsync(new RequestLimit
// {
// Username = Username,
// RequestType = model.Type,
// FirstRequestDate = DateTime.UtcNow,
// RequestCount = 1
// });
//}
//else
//{
// usersLimit.RequestCount++;
// await RequestLimitRepo.UpdateAsync(usersLimit);
//}
return new RequestEngineResult { RequestAdded = true };
}
public async Task<IEnumerable<MovieRequestModel>> GetMovieRequests(int count, int position)
{
var allRequests = await MovieRequestService.GetAllAsync(count, position);
return allRequests;
}
public async Task<IEnumerable<MovieRequestModel>> SearchMovieRequest(string search)
{
var allRequests = await MovieRequestService.GetAllAsync();
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase));
return results;
}
public async Task<MovieRequestModel> UpdateMovieRequest(MovieRequestModel request)
{
var allRequests = await MovieRequestService.GetAllAsync();
var results = allRequests.FirstOrDefault(x => x.Id == request.Id);
results.Approved = request.Approved;
results.Available = request.Available;
results.Denied = request.Denied;
results.DeniedReason = request.DeniedReason;
results.AdminNote = request.AdminNote;
results.ImdbId = request.ImdbId;
results.IssueId = request.IssueId;
results.Issues = request.Issues;
results.OtherMessage = request.OtherMessage;
results.Overview = request.Overview;
results.PosterPath = request.PosterPath;
results.RequestedUsers = request.RequestedUsers?.ToList() ?? new List<string>();
var model = MovieRequestService.UpdateRequest(results);
return model;
}
public async Task RemoveMovieRequest(int requestId)
{
await MovieRequestService.DeleteRequestAsync(requestId);
}
}
}

View file

@ -0,0 +1,185 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.IdentityResolver;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Requests.Models;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine
{
public class MovieSearchEngine : BaseMediaEngine, IMovieEngine
{
public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings,
ILogger<MovieSearchEngine> logger)
: base(identity, service)
{
MovieApi = movApi;
Mapper = mapper;
PlexSettings = plexSettings;
EmbySettings = embySettings;
Logger = logger;
}
private IMovieDbApi MovieApi { get; }
private IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ILogger<MovieSearchEngine> Logger { get; }
public async Task<IEnumerable<SearchMovieViewModel>> LookupImdbInformation(IEnumerable<SearchMovieViewModel> movies)
{
var searchMovieViewModels
= movies as IList<SearchMovieViewModel> ?? movies.ToList();
if (searchMovieViewModels == null || !searchMovieViewModels.Any())
{
return new List<SearchMovieViewModel>();
}
var retVal = new List<SearchMovieViewModel>();
Dictionary<int, MovieRequestModel> dbMovies = await GetMovieRequests();
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
foreach (var m in searchMovieViewModels)
{
var movieInfo = await MovieApi.GetMovieInformationWithVideo(m.Id);
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo);
retVal.Add(await ProcessSingleMovie(viewMovie, dbMovies, plexSettings, embySettings));
}
return retVal;
}
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
{
var result = await MovieApi.SearchMovie(search);
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result);
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
{
var result = await MovieApi.PopularMovies();
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result);
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
{
var result = await MovieApi.TopRated();
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result);
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
{
var result = await MovieApi.Upcoming();
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result);
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
{
var result = await MovieApi.NowPlaying();
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result);
}
return null;
}
private async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(IEnumerable<MovieSearchResult> movies)
{
var viewMovies = new List<SearchMovieViewModel>();
Dictionary<int, MovieRequestModel> dbMovies = await GetMovieRequests();
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
foreach (var movie in movies)
{
viewMovies.Add(await ProcessSingleMovie(movie, dbMovies, plexSettings, embySettings));
}
return viewMovies;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie,
Dictionary<int, MovieRequestModel> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
{
if (plexSettings.Enable)
{
// var content = PlexContentRepository.GetAll();
// var plexMovies = PlexChecker.GetPlexMovies(content);
// var plexMovie = PlexChecker.GetMovie(plexMovies.ToArray(), movie.Title,
// movie.ReleaseDate?.Year.ToString(),
// viewMovie.ImdbId);
// if (plexMovie != null)
// {
// viewMovie.Available = true;
// viewMovie.PlexUrl = plexMovie.Url;
// }
}
if (embySettings.Enable)
{
// var embyContent = EmbyContentRepository.GetAll();
// var embyMovies = EmbyChecker.GetEmbyMovies(embyContent);
// var embyMovie = EmbyChecker.GetMovie(embyMovies.ToArray(), movie.Title,
// movie.ReleaseDate?.Year.ToString(), viewMovie.ImdbId);
// if (embyMovie != null)
// {
// viewMovie.Available = true;
// }
}
if (existingRequests.ContainsKey(viewMovie.Id)) // Do we already have a request for this?
{
var requestedMovie = existingRequests[viewMovie.Id];
viewMovie.Requested = true;
viewMovie.Approved = requestedMovie.Approved;
viewMovie.Available = requestedMovie.Available;
}
return viewMovie;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieSearchResult movie, Dictionary<int, MovieRequestModel> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
{
var viewMovie = Mapper.Map<SearchMovieViewModel>(movie);
return await ProcessSingleMovie(viewMovie, existingRequests, plexSettings, embySettings);
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ombi.Core.Engine
{
public class RequestEngineResult
{
public bool RequestAdded { get; set; }
public string Message { get; set; }
public bool IsError => !string.IsNullOrEmpty(ErrorMessage);
public string ErrorMessage { get; set; }
}
}

View file

@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using AutoMapper;
using Hangfire;
using Ombi.Api.TvMaze;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Store.Entities;
using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Notifications.Models;
namespace Ombi.Core.Engine
{
public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine
{
public TvRequestEngine(ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user, INotificationService notificationService, IMapper map) : base(user, requestService)
{
TvApi = tvApi;
NotificationService = notificationService;
Mapper = map;
}
private INotificationService NotificationService { get; }
private ITvMazeApi TvApi { get; }
private IMapper Mapper { get; }
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv)
{
var showInfo = await TvApi.ShowLookupByTheTvDbId(tv.Id);
DateTime.TryParse(showInfo.premiered, out DateTime firstAir);
// For some reason the poster path is always http
var posterPath = showInfo.image?.medium.Replace("http:", "https:");
var model = new TvRequestModel
{
Id = tv.Id,
Type = RequestType.TvShow,
Overview = showInfo.summary.RemoveHtml(),
PosterPath = posterPath,
Title = showInfo.name,
ReleaseDate = firstAir,
Status = showInfo.status,
RequestedDate = DateTime.UtcNow,
Approved = false,
RequestedUsers = new List<string> { Username },
Issues = IssueState.None,
ImdbId = showInfo.externals?.imdb ?? string.Empty,
TvDbId = tv.Id.ToString(),
ProviderId = tv.Id,
RequestAll = tv.RequestAll
};
var episodes = await TvApi.EpisodeLookup(showInfo.id);
foreach (var e in episodes)
{
var season = model.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season);
season?.Episodes.Add(new EpisodesRequested
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp),
EpisodeNumber = e.number,
});
}
if (tv.LatestSeason)
{
var latest = showInfo.Season.OrderBy(x => x.SeasonNumber).FirstOrDefault();
foreach (var modelSeasonRequest in model.SeasonRequests)
{
if (modelSeasonRequest.SeasonNumber == latest.SeasonNumber)
{
foreach (var episodesRequested in modelSeasonRequest.Episodes)
{
episodesRequested.Requested = true;
}
}
}
}
if (tv.FirstSeason)
{
var first = showInfo.Season.OrderByDescending(x => x.SeasonNumber).FirstOrDefault();
foreach (var modelSeasonRequest in model.SeasonRequests)
{
if (modelSeasonRequest.SeasonNumber == first.SeasonNumber)
{
foreach (var episodesRequested in modelSeasonRequest.Episodes)
{
episodesRequested.Requested = true;
}
}
}
}
var existingRequest = await TvRequestService.CheckRequestAsync(model.Id);
if (existingRequest != null)
{
return await AddExistingRequest(model, existingRequest);
}
// This is a new request
return await AddRequest(model);
}
public async Task<IEnumerable<TvRequestModel>> GetTvRequests(int count, int position)
{
var allRequests = await TvRequestService.GetAllAsync(count, position);
return allRequests;
}
public async Task<IEnumerable<TvRequestModel>> SearchTvRequest(string search)
{
var allRequests = await TvRequestService.GetAllAsync();
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase));
return results;
}
public async Task<TvRequestModel> UpdateTvRequest(TvRequestModel request)
{
var allRequests = await TvRequestService.GetAllAsync();
var results = allRequests.FirstOrDefault(x => x.Id == request.Id);
results.Approved = request.Approved;
results.Available = request.Available;
results.Denied = request.Denied;
results.DeniedReason = request.DeniedReason;
results.AdminNote = request.AdminNote;
results.ImdbId = request.ImdbId;
results.IssueId = request.IssueId;
results.Issues = request.Issues;
results.OtherMessage = request.OtherMessage;
results.Overview = request.Overview;
results.PosterPath = request.PosterPath;
results.RequestedUsers = request.RequestedUsers?.ToList() ?? new List<string>();
var model = TvRequestService.UpdateRequest(results);
return model;
}
public async Task RemoveTvRequest(int requestId)
{
await TvRequestService.DeleteRequestAsync(requestId);
}
private async Task<RequestEngineResult> AddExistingRequest(TvRequestModel newRequest, TvRequestModel existingRequest)
{
var episodeDifference = new List<SeasonRequestModel>();
if (existingRequest.HasChildRequests)
{
// Let's check if this has already been requested as a child!
foreach (var children in existingRequest.ChildRequests)
{
var difference = GetListDifferences(children.SeasonRequests, newRequest.SeasonRequests).ToList();
if (difference.Any())
{
episodeDifference = difference;
}
}
}
if (episodeDifference.Any())
{
// This is where there are some episodes that have been requested, but this list contains the 'new' requests
newRequest.SeasonRequests = episodeDifference;
}
if (!existingRequest.HasChildRequests)
{
// So this is the first child request, we will want to convert the original request to a child
var originalRequest = Mapper.Map<TvRequestModel>(existingRequest);
existingRequest.ChildRequests.Add(originalRequest);
existingRequest.RequestedUsers.Clear();
existingRequest.Approved = false;
existingRequest.Available = false;
}
existingRequest.ChildRequests.Add(newRequest);
TvRequestService.UpdateRequest(existingRequest);
if (ShouldAutoApprove(RequestType.TvShow))
{
// TODO Auto Approval Code
}
return await AfterRequest(newRequest);
}
private IEnumerable<SeasonRequestModel> GetListDifferences(IEnumerable<SeasonRequestModel> existing, IEnumerable<SeasonRequestModel> request)
{
var newRequest = request
.Select(r =>
new SeasonRequestModel
{
SeasonNumber = r.SeasonNumber,
Episodes = r.Episodes
}).ToList();
return newRequest.Except(existing);
}
private async Task<RequestEngineResult> AddRequest(TvRequestModel model)
{
await TvRequestService.AddRequestAsync(model);
return await AfterRequest(model);
}
private async Task<RequestEngineResult> AfterRequest(BaseRequestModel model)
{
if (ShouldSendNotification(model.Type))
{
var notificationModel = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = model.Type,
ImgSrc = model.PosterPath
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel).Wait());
}
//var limit = await RequestLimitRepo.GetAllAsync();
//var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type);
//if (usersLimit == null)
//{
// await RequestLimitRepo.InsertAsync(new RequestLimit
// {
// Username = Username,
// RequestType = model.Type,
// FirstRequestDate = DateTime.UtcNow,
// RequestCount = 1
// });
//}
//else
//{
// usersLimit.RequestCount++;
// await RequestLimitRepo.UpdateAsync(usersLimit);
//}
return new RequestEngineResult { RequestAdded = true };
}
}
}

View file

@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using AutoMapper;
using Ombi.Api.Trakt;
using Ombi.Api.TvMaze;
using Ombi.Api.TvMaze.Models;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Requests.Models;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine
{
public class TvSearchEngine : BaseMediaEngine, ITvSearchEngine
{
public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmbySettings> embySettings)
: base(identity, service)
{
TvMazeApi = tvMaze;
Mapper = mapper;
PlexSettings = plexSettings;
EmbySettings = embySettings;
//TraktApi = trakt;
}
private ITvMazeApi TvMazeApi { get; }
private IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
//private ITraktApi TraktApi { get; }
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm)
{
var searchResult = await TvMazeApi.Search(searchTerm);
if (searchResult != null)
{
return await ProcessResults(searchResult);
}
return null;
}
public async Task<SearchTvShowViewModel> GetShowInformation(int tvmazeId)
{
var show = await TvMazeApi.ShowLookup(tvmazeId);
var episodes = await TvMazeApi.EpisodeLookup(show.id);
var mapped = Mapper.Map<SearchTvShowViewModel>(show);
foreach (var e in episodes)
{
var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season);
if (season == null)
{
var newSeason = new SeasonRequestModel
{
SeasonNumber = e.season,
};
newSeason.Episodes.Add(new EpisodesRequested
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp),
EpisodeNumber = e.number,
});
mapped.SeasonRequests.Add(newSeason);
}
else
{
season.Episodes.Add(new EpisodesRequested
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp),
EpisodeNumber = e.number,
});
}
}
var existingRequests = await GetTvRequests();
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
return ProcessResult(mapped, existingRequests, plexSettings, embySettings);
}
//public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
//{
// var result = await TraktApi.GetPopularShows();
// return await ProcessResults(result);
//}
//public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
//{
// var result = await TraktApi.GetAnticipatedShows();
// return await ProcessResults(result);
//}
//public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
//{
// var result = await TraktApi.GetMostWatchesShows();
// return await ProcessResults(result);
//}
//public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
//{
// var result = await TraktApi.GetTrendingShows();
// return await ProcessResults(result);
//}
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
{
var existingRequests = await GetTvRequests();
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
{
var viewT = Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
retVal.Add(ProcessResult(viewT, existingRequests, plexSettings, embySettings));
}
return retVal;
}
private SearchTvShowViewModel ProcessResult(SearchTvShowViewModel item, Dictionary<int, TvRequestModel> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
{
if (embySettings.Enable)
{
//var embyShow = EmbyChecker.GetTvShow(embyCached.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId);
//if (embyShow != null)
//{
// viewT.Available = true;
//}
}
if (plexSettings.Enable)
{
//var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4),
// providerId);
//if (plexShow != null)
//{
// viewT.Available = true;
// viewT.PlexUrl = plexShow.Url;
//}
}
if (item.Id > 0 && item.Available)
{
var tvdbid = item.Id;
if (existingRequests.ContainsKey(tvdbid))
{
var existingRequest = existingRequests[tvdbid];
item.Requested = true;
item.Approved = existingRequest.Approved;
// Let's modify the seasonsrequested to reflect what we have requested...
foreach (var season in item.SeasonRequests)
{
// Find the existing request season
var existingSeason =
existingRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber);
foreach (var ep in existingSeason.Episodes)
{
// Find the episode from what we are searching
var episodeSearching = season.Episodes.FirstOrDefault(x => x.EpisodeNumber == ep.EpisodeNumber);
episodeSearching.Requested = ep.Requested;
episodeSearching.Available = ep.Available;
episodeSearching.Approved = ep.Approved;
}
}
}
//if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid))
// // compare to the sonarr/sickrage db
//{
// item.Requested = true;
//}
}
return item;
}
}
}

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models;
namespace Ombi.Core.IdentityResolver
{
public interface IUserIdentityManager
{
Task<UserDto> CreateUser(UserDto user);
Task<bool> CredentialsValid(string username, string password);
Task<UserDto> GetUser(string username);
Task<IEnumerable<UserDto>> GetUsers();
Task DeleteUser(UserDto user);
Task<UserDto> UpdateUser(UserDto userDto);
}
}

View file

@ -0,0 +1,126 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: UserIdentityManager.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using Ombi.Core.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.IdentityResolver
{
public class UserIdentityManager : IUserIdentityManager
{
public UserIdentityManager(IUserRepository userRepository, IMapper mapper)
{
UserRepository = userRepository;
Mapper = mapper;
}
private IMapper Mapper { get; }
private IUserRepository UserRepository { get; }
public async Task<bool> CredentialsValid(string username, string password)
{
var user = await UserRepository.GetUser(username);
if (user == null) return false;
var hash = HashPassword(password, user.Salt);
return hash.HashedPass.Equals(user.Password);
}
public async Task<UserDto> GetUser(string username)
{
return Mapper.Map<UserDto>(await UserRepository.GetUser(username));
}
public async Task<IEnumerable<UserDto>> GetUsers()
{
return Mapper.Map<List<UserDto>>(await UserRepository.GetUsers());
}
public async Task<UserDto> CreateUser(UserDto userDto)
{
var user = Mapper.Map<User>(userDto);
user.Claims.RemoveAll(x => x.Type == ClaimTypes.Country); // This is a hack around the Mapping Profile
var result = HashPassword(user.Password);
user.Password = result.HashedPass;
user.Salt = result.Salt;
await UserRepository.CreateUser(user);
return Mapper.Map<UserDto>(user);
}
public async Task DeleteUser(UserDto user)
{
await UserRepository.DeleteUser(Mapper.Map<User>(user));
}
public async Task<UserDto> UpdateUser(UserDto userDto)
{
var user = Mapper.Map<User>(userDto);
return Mapper.Map<UserDto>(await UserRepository.UpdateUser(user));
}
private UserHash HashPassword(string password)
{
// generate a 128-bit salt using a secure PRNG
byte[] salt = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
return HashPassword(password, salt);
}
private UserHash HashPassword(string password, byte[] salt)
{
// derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
var hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));
return new UserHash { HashedPass = hashed, Salt = salt };
}
private class UserHash
{
public string HashedPass { get; set; }
public byte[] Salt { get; set; }
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Core.Models
{
public class QualityModel
{
public string Id { get; set; }
public string Name { get; set; }
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Requests
{
public class BaseRequestModel : Entity
{
public BaseRequestModel()
{
RequestedUsers = new List<string>();
}
public int ProviderId { get; set; }
public string Overview { get; set; }
public string Title { get; set; }
public string PosterPath { get; set; }
public DateTime ReleaseDate { get; set; }
public RequestType Type { get; set; }
public string Status { get; set; }
public bool Approved { get; set; }
public bool Admin { get; set; }
public DateTime RequestedDate { get; set; }
public bool Available { get; set; }
public IssueState Issues { get; set; }
public string OtherMessage { get; set; }
public string AdminNote { get; set; }
public List<string> RequestedUsers { get; set; }
public int IssueId { get; set; }
public bool Denied { get; set; }
public string DeniedReason { get; set; }
[JsonIgnore]
public bool Released => DateTime.UtcNow > ReleaseDate;
[JsonIgnore]
public IEnumerable<string> AllUsers
{
get
{
var u = new List<string>();
if (RequestedUsers != null && RequestedUsers.Any())
{
u.AddRange(RequestedUsers);
}
return u;
}
}
[JsonIgnore]
public bool CanApprove => !Approved && !Available;
public bool UserHasRequested(string username)
{
return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase));
}
}
}

View file

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Store.Entities;
namespace Ombi.Core.Requests.Models
{
public interface IRequestService<T> where T : BaseRequestModel
{
int AddRequest(T model);
Task<int> AddRequestAsync(T model);
void BatchDelete(IEnumerable<T> model);
void BatchUpdate(IEnumerable<T> model);
T CheckRequest(int providerId);
Task<T> CheckRequestAsync(int providerId);
void DeleteRequest(T request);
Task DeleteRequestAsync(int request);
Task DeleteRequestAsync(T request);
T Get(int id);
IEnumerable<T> GetAll();
Task<IEnumerable<T>> GetAllAsync();
Task<IEnumerable<T>> GetAllAsync(int count, int position);
Task<T> GetAsync(int id);
T UpdateRequest(T model);
}
}

View file

@ -0,0 +1,179 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Ombi.Core.Requests.Models;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Models.Requests
{
public class JsonRequestService<T> : IRequestService<T> where T : BaseRequestModel
{
public JsonRequestService(IRequestRepository repo)
{
Repo = repo;
RequestType = typeof(T) == typeof(TvRequestModel) ? RequestType.TvShow : RequestType.Movie;
}
private RequestType RequestType { get; }
private IRequestRepository Repo { get; }
public int AddRequest(T model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId };
var id = Repo.Insert(entity);
return id.Id;
}
public async Task<int> AddRequestAsync(T model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId };
var id = await Repo.InsertAsync(entity).ConfigureAwait(false);
return id.Id;
}
public T CheckRequest(int providerId)
{
var blobs = Repo.GetAll();
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId && x.Type == RequestType);
if (blob == null)
{
return null;
}
var model = ByteConverterHelper.ReturnObject<T>(blob.Content);
model.Id = blob.Id;
return model;
}
public async Task<T> CheckRequestAsync(int providerId)
{
var blobs = await Repo.GetAllAsync().ConfigureAwait(false);
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId && x.Type == RequestType); if (blob == null)
{
return null;
}
var model = ByteConverterHelper.ReturnObject<T>(blob.Content);
model.Id = blob.Id;
return model;
}
public void DeleteRequest(T request)
{
var blob = Repo.Get(request.Id);
Repo.Delete(blob);
}
public async Task DeleteRequestAsync(T request)
{
var blob = await Repo.GetAsync(request.Id).ConfigureAwait(false);
Repo.Delete(blob);
}
public async Task DeleteRequestAsync(int request)
{
var blob = await Repo.GetAsync(request).ConfigureAwait(false);
Repo.Delete(blob);
}
public T UpdateRequest(T model)
{
var b = Repo.Get(model.Id);
b.Content = ByteConverterHelper.ReturnBytes(model);
var blob = Repo.Update(b);
return model;
}
public T Get(int id)
{
var blob = Repo.Get(id);
if (blob == null)
{
return default(T);
}
var model = ByteConverterHelper.ReturnObject<T>(blob.Content);
model.Id = blob.Id; // They should always be the same, but for somereason a user didn't have it in the db https://github.com/tidusjar/Ombi/issues/862#issuecomment-269743847
return model;
}
public async Task<T> GetAsync(int id)
{
var blob = await Repo.GetAsync(id).ConfigureAwait(false);
if (blob == null)
{
return default(T);
}
var model = ByteConverterHelper.ReturnObject<T>(blob.Content);
model.Id = blob.Id;
return model;
}
public IEnumerable<T> GetAll()
{
var blobs = Repo.GetAll().Where(x => x.Type == RequestType).ToList();
var retVal = new List<T>();
foreach (var b in blobs)
{
if (b == null)
{
continue;
}
var model = ByteConverterHelper.ReturnObject<T>(b.Content);
model.Id = b.Id;
retVal.Add(model);
}
return retVal;
}
public async Task<IEnumerable<T>> GetAllAsync()
{
var blobs = await Repo.GetAllAsync().ConfigureAwait(false);
var retVal = new List<T>();
foreach (var b in blobs.Where(x => x.Type == RequestType))
{
if (b == null)
{
continue;
}
var model = ByteConverterHelper.ReturnObject<T>(b.Content);
model.Id = b.Id;
retVal.Add(model);
}
return retVal;
}
public async Task<IEnumerable<T>> GetAllAsync(int count, int position)
{
var blobs = await Repo.GetAllAsync(count, position).ConfigureAwait(false);
var retVal = new List<T>();
foreach (var b in blobs.Where(x => x.Type == RequestType))
{
if (b == null)
{
continue;
}
var model = ByteConverterHelper.ReturnObject<T>(b.Content);
model.Id = b.Id;
retVal.Add(model);
}
return retVal;
}
public void BatchUpdate(IEnumerable<T> model)
{
var entities = model.Select(m => new RequestBlobs { Type = m.Type, Content = ByteConverterHelper.ReturnBytes(m), ProviderId = m.ProviderId, Id = m.Id }).ToList();
Repo.UpdateAll(entities);
}
public void BatchDelete(IEnumerable<T> model)
{
var entities = model.Select(m => new RequestBlobs { Type = m.Type, Content = ByteConverterHelper.ReturnBytes(m), ProviderId = m.ProviderId, Id = m.Id }).ToList();
Repo.DeleteAll(entities);
}
}
}

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Requests
{
public class MovieRequestModel : BaseRequestModel
{
public string ImdbId { get; set; }
}
}

View file

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Requests
{
public static class RequestTypeDisplay
{
public static string GetString(this RequestType type)
{
switch (type)
{
case RequestType.Movie:
return "Movie";
case RequestType.TvShow:
return "TV Show";
default:
return string.Empty;
}
}
}
public enum IssueState
{
None = 99,
WrongAudio = 0,
NoSubtitles = 1,
WrongContent = 2,
PlaybackIssues = 3,
Other = 4, // Provide a message
}
public class EpisodesModel : IEquatable<EpisodesModel>
{
public int SeasonNumber { get; set; }
public int EpisodeNumber { get; set; }
public bool Equals(EpisodesModel other)
{
// Check whether the compared object is null.
if (ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (ReferenceEquals(this, other)) return true;
//Check whether the properties are equal.
return SeasonNumber.Equals(other.SeasonNumber) && EpisodeNumber.Equals(other.EpisodeNumber);
}
public override int GetHashCode()
{
var hashSeason = SeasonNumber.GetHashCode();
var hashEp = EpisodeNumber.GetHashCode();
//Calculate the hash code.
return hashSeason + hashEp;
}
}
public class SeasonRequestModel
{
public int SeasonNumber { get; set; }
public List<EpisodesRequested> Episodes { get; set; } = new List<EpisodesRequested>();
}
public class EpisodesRequested
{
public int EpisodeNumber { get; set; }
public string Title { get; set; }
public DateTime AirDate { get; set; }
public string Url { get; set; }
public bool Requested { get; set; }
public string Status { get; set; }
public bool Available { get; set; }
public bool Approved { get; set; }
}
}

View file

@ -0,0 +1,16 @@
using Ombi.Core.Requests.Models;
namespace Ombi.Core.Models.Requests
{
public class RequestService : IRequestServiceMain
{
public RequestService(IRequestService<TvRequestModel> tv, IRequestService<MovieRequestModel> movie)
{
TvRequestService = tv;
MovieRequestService = movie;
}
public IRequestService<TvRequestModel> TvRequestService { get; }
public IRequestService<MovieRequestModel> MovieRequestService { get; }
}
}

View file

@ -0,0 +1,33 @@
using System;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Requests
{
public class RequestViewModel
{
public int Id { get; set; }
public int ProviderId { get; set; }
public string ImdbId { get; set; }
public string Overview { get; set; }
public string Title { get; set; }
public string PosterPath { get; set; }
public DateTime ReleaseDate { get; set; }
public bool Released { get; set; }
public RequestType Type { get; set; }
public string Status { get; set; }
public bool Approved { get; set; }
public string[] RequestedUsers { get; set; }
public DateTime RequestedDate { get; set; }
public string ReleaseYear { get; set; }
public bool Available { get; set; }
public bool Admin { get; set; }
public int IssueId { get; set; }
public QualityModel[] Qualities { get; set; }
public EpisodesModel[] Episodes { get; set; }
public bool Denied { get; set; }
public string DeniedReason { get; set; }
public RootFolderModel[] RootFolders { get; set; }
public bool HasRootFolders { get; set; }
public string CurrentRootPath { get; set; }
}
}

View file

@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace Ombi.Core.Models.Requests
{
public class TvRequestModel : BaseRequestModel
{
public TvRequestModel()
{
SeasonRequests = new List<SeasonRequestModel>();
ChildRequests = new List<TvRequestModel>();
}
public string ImdbId { get; set; }
public string TvDbId { get; set; }
public bool RequestAll { get; set; }
public List<SeasonRequestModel> SeasonRequests { get; set; }
/// <summary>
/// This is for TV requests, If there is more than 1 request for a show then it should be a child
/// e.g. Request 1 is for Season 1, Request 2 is for season 5. There should be two child items.
/// </summary>
public List<TvRequestModel> ChildRequests { get; set; }
public bool HasChildRequests => ChildRequests.Count > 0;
/// <summary>
/// For TV Shows with a custom root folder
/// </summary>
/// <value>
/// The root folder selected.
/// </value>
public int RootFolderSelected { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Ombi.Core.Models.Requests
{
public class RootFolderModel
{
public string Id { get; set; }
public string Path { get; set; }
public long FreeSpace { get; set; }
}
}

View file

@ -0,0 +1,54 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: SearchMovieViewModel.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
namespace Ombi.Core.Models.Search
{
public class SearchMovieViewModel : SearchViewModel
{
public bool Adult { get; set; }
public string BackdropPath { get; set; }
public List<int> GenreIds { get; set; }
public int Id { get; set; }
public string OriginalLanguage { get; set; }
public string OriginalTitle { get; set; }
public string Overview { get; set; }
public double Popularity { get; set; }
public string PosterPath { get; set; }
public DateTime? ReleaseDate { get; set; }
public string Title { get; set; }
public bool Video { get; set; }
public double VoteAverage { get; set; }
public int VoteCount { get; set; }
public bool AlreadyInCp { get; set; }
public string Trailer { get; set; }
public string Homepage { get; set; }
public string ImdbId { get; set; }
}
}

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using Ombi.Core.Models.Requests;
namespace Ombi.Core.Models.Search
{
public class SearchTvShowViewModel : SearchViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public List<string> Aliases { get; set; }
public string Banner { get; set; }
public int SeriesId { get; set; }
public string Status { get; set; }
public string FirstAired { get; set; }
public string Network { get; set; }
public string NetworkId { get; set; }
public string Runtime { get; set; }
public List<string> Genre { get; set; }
public string Overview { get; set; }
public int LastUpdated { get; set; }
public string AirsDayOfWeek { get; set; }
public string AirsTime { get; set; }
public string Rating { get; set; }
public string ImdbId { get; set; }
public int SiteRating { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Trailer { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Homepage { get; set; }
public List<SeasonRequestModel> SeasonRequests { get; set; } = new List<SeasonRequestModel>();
/// <summary>
/// If we are requesting the entire series
/// </summary>
public bool RequestAll { get; set; }
public bool FirstSeason { get; set; }
public bool LatestSeason { get; set; }
}
}

View file

@ -0,0 +1,36 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: SearchViewModel.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 Ombi.Core.Models.Search
{
public class SearchViewModel
{
public bool Approved { get; set; }
public bool Requested { get; set; }
public bool Available { get; set; }
public string PlexUrl { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show more