Merge branch 'dev' into master

pull/1178/head
Shaun McPeck 8 years ago committed by GitHub
commit f817953ba3

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using Ombi.Api.Models.Emby;
namespace Ombi.Api.Interfaces
{
public interface IEmbyApi
{
EmbyItemContainer<EmbyMovieItem> GetAllMovies(string apiKey, string userId, Uri baseUri);
EmbyItemContainer<EmbySeriesItem> GetAllShows(string apiKey, string userId, Uri baseUri);
EmbyItemContainer<EmbyEpisodeItem> GetAllEpisodes(string apiKey, string userId, Uri baseUri);
EmbyItemContainer<EmbyMovieInformation> GetCollection(string mediaId, string apiKey, string userId, Uri baseUrl);
List<EmbyUser> GetUsers(Uri baseUri, string apiKey);
EmbyItemContainer<EmbyLibrary> ViewLibrary(string apiKey, string userId, Uri baseUri);
EmbyInformation GetInformation(string mediaId, EmbyMediaType type, string apiKey, string userId, Uri baseUri);
EmbyUser LogIn(string username, string password, string apiKey, Uri baseUri);
EmbySystemInfo GetSystemInformation(string apiKey, Uri baseUrl);
}
}

@ -11,5 +11,6 @@ namespace Ombi.Api.Interfaces
List<RadarrMovieResponse> GetMovies(string apiKey, Uri baseUrl); List<RadarrMovieResponse> GetMovies(string apiKey, Uri baseUrl);
List<SonarrProfile> GetProfiles(string apiKey, Uri baseUrl); List<SonarrProfile> GetProfiles(string apiKey, Uri baseUrl);
SystemStatus SystemStatus(string apiKey, Uri baseUrl); SystemStatus SystemStatus(string apiKey, Uri baseUrl);
List<SonarrRootFolder> GetRootFolders(string apiKey, Uri baseUrl);
} }
} }

@ -56,6 +56,7 @@
<Compile Include="IApiRequest.cs" /> <Compile Include="IApiRequest.cs" />
<Compile Include="ICouchPotatoApi.cs" /> <Compile Include="ICouchPotatoApi.cs" />
<Compile Include="IDiscordApi.cs" /> <Compile Include="IDiscordApi.cs" />
<Compile Include="IEmbyApi.cs" />
<Compile Include="IHeadphonesApi.cs" /> <Compile Include="IHeadphonesApi.cs" />
<Compile Include="IMusicBrainzApi.cs" /> <Compile Include="IMusicBrainzApi.cs" />
<Compile Include="INetflixApi.cs" /> <Compile Include="INetflixApi.cs" />

@ -0,0 +1,37 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyChapter
{
public long StartPositionTicks { get; set; }
public string Name { get; set; }
}
}

@ -0,0 +1,47 @@
#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
namespace Ombi.Api.Models.Emby
{
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; }
}
}

@ -0,0 +1,97 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyEpisodeInformation.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.Models.Emby
{
public class EmbyEpisodeInformation
{
public string Name { get; set; }
public string ServerId { get; set; }
public string Id { get; set; }
public string Etag { get; set; }
public DateTime DateCreated { get; set; }
public bool CanDelete { get; set; }
public bool CanDownload { get; set; }
public bool SupportsSync { get; set; }
public string Container { get; set; }
public string SortName { get; set; }
public DateTime PremiereDate { get; set; }
public EmbyExternalurl[] ExternalUrls { get; set; }
public EmbyMediasource[] MediaSources { get; set; }
public string Path { get; set; }
public string Overview { get; set; }
public object[] Taglines { get; set; }
public object[] Genres { get; set; }
public string[] SeriesGenres { get; set; }
public float CommunityRating { get; set; }
public int VoteCount { get; set; }
public long RunTimeTicks { get; set; }
public string PlayAccess { get; set; }
public int ProductionYear { get; set; }
public bool IsPlaceHolder { get; set; }
public int IndexNumber { get; set; }
public int ParentIndexNumber { get; set; }
public object[] RemoteTrailers { get; set; }
public EmbyProviderids ProviderIds { get; set; }
public bool IsHD { get; set; }
public bool IsFolder { get; set; }
public string ParentId { get; set; }
public string Type { get; set; }
public object[] People { get; set; }
public object[] Studios { get; set; }
public string ParentLogoItemId { get; set; }
public string ParentBackdropItemId { get; set; }
public string[] ParentBackdropImageTags { get; set; }
public int LocalTrailerCount { get; set; }
public EmbyUserdata UserData { get; set; }
public string SeriesName { get; set; }
public string SeriesId { get; set; }
public string SeasonId { get; set; }
public string DisplayPreferencesId { get; set; }
public object[] Tags { get; set; }
public object[] Keywords { get; set; }
public string SeriesPrimaryImageTag { get; set; }
public string SeasonName { get; set; }
public EmbyMediastream[] MediaStreams { get; set; }
public string VideoType { get; set; }
public EmbyImagetags ImageTags { get; set; }
public object[] BackdropImageTags { get; set; }
public object[] ScreenshotImageTags { get; set; }
public string ParentLogoImageTag { get; set; }
public string SeriesStudio { get; set; }
public EmbySeriesstudioinfo SeriesStudioInfo { get; set; }
public string ParentThumbItemId { get; set; }
public string ParentThumbImageTag { get; set; }
public EmbyChapter[] Chapters { get; set; }
public string LocationType { get; set; }
public string MediaType { get; set; }
public object[] LockedFields { get; set; }
public bool LockData { get; set; }
}
}

@ -0,0 +1,69 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyEpisodeItem.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.Models.Emby
{
public class EmbyEpisodeItem
{
public string Name { get; set; }
public string ServerId { get; set; }
public string Id { get; set; }
public string Container { get; set; }
public DateTime PremiereDate { get; set; }
public float CommunityRating { get; set; }
public long RunTimeTicks { get; set; }
public string PlayAccess { get; set; }
public int ProductionYear { get; set; }
public bool IsPlaceHolder { get; set; }
public int IndexNumber { get; set; }
public int ParentIndexNumber { get; set; }
public bool IsHD { get; set; }
public bool IsFolder { get; set; }
public string Type { get; set; }
public string ParentLogoItemId { get; set; }
public string ParentBackdropItemId { get; set; }
public string[] ParentBackdropImageTags { get; set; }
public int LocalTrailerCount { get; set; }
public EmbyUserdata UserData { get; set; }
public string SeriesName { get; set; }
public string SeriesId { get; set; }
public string SeasonId { get; set; }
public string SeriesPrimaryImageTag { get; set; }
public string SeasonName { get; set; }
public string VideoType { get; set; }
public EmbyImagetags ImageTags { get; set; }
public object[] BackdropImageTags { get; set; }
public string ParentLogoImageTag { get; set; }
public string ParentThumbItemId { get; set; }
public string ParentThumbImageTag { get; set; }
public string LocationType { get; set; }
public string MediaType { get; set; }
public bool HasSubtitles { get; set; }
}
}

@ -0,0 +1,42 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyExternalurl
{
public string Name { get; set; }
public string Url { get; set; }
}
}

@ -0,0 +1,37 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyLibrary.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.Models.Emby
{
public class EmbyImagetags
{
public string Primary { get; set; }
public string Logo { get; set; }
public string Thumb { get; set; }
public string Banner { get; set; }
}
}

@ -0,0 +1,35 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyInformation.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.Models.Emby
{
public class EmbyInformation
{
public EmbySeriesInformation SeriesInformation { get; set; }
public EmbyMovieInformation MovieInformation { get; set; }
public EmbyEpisodeInformation EpisodeInformation { get; set; }
}
}

@ -0,0 +1,47 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyLibrary.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.Models.Emby
{
public class EmbyLibrary
{
public string Name { get; set; }
public string ServerId { get; set; }
public string Id { get; set; }
public bool HasDynamicCategories { get; set; }
public string PlayAccess { get; set; }
public bool IsFolder { get; set; }
public string Type { get; set; }
public EmbyUserdata UserData { get; set; }
public int ChildCount { get; set; }
public string CollectionType { get; set; }
public string OriginalCollectionType { get; set; }
public EmbyImagetags ImageTags { get; set; }
public object[] BackdropImageTags { get; set; }
public string LocationType { get; set; }
}
}

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

@ -0,0 +1,36 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyMediaType.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.Models.Emby
{
public enum EmbyMediaType
{
Movie = 0,
Series = 1,
Music = 2,
Episode = 3
}
}

@ -0,0 +1,59 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyMediasource
{
public string Protocol { get; set; }
public string Id { get; set; }
public string Path { get; set; }
public string Type { get; set; }
public string Container { get; set; }
public string Name { get; set; }
public bool IsRemote { get; set; }
public string ETag { get; set; }
public long RunTimeTicks { get; set; }
public bool ReadAtNativeFramerate { get; set; }
public bool SupportsTranscoding { get; set; }
public bool SupportsDirectStream { get; set; }
public bool SupportsDirectPlay { get; set; }
public bool IsInfiniteStream { get; set; }
public bool RequiresOpening { get; set; }
public bool RequiresClosing { get; set; }
public bool SupportsProbing { get; set; }
public string VideoType { get; set; }
public EmbyMediastream[] MediaStreams { get; set; }
public object[] PlayableStreamFileNames { get; set; }
public object[] Formats { get; set; }
public int Bitrate { get; set; }
public EmbyRequiredhttpheaders RequiredHttpHeaders { get; set; }
public int DefaultAudioStreamIndex { get; set; }
}
}

@ -0,0 +1,64 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyMediastream
{
public string Codec { get; set; }
public string Language { get; set; }
public string TimeBase { get; set; }
public string CodecTimeBase { get; set; }
public string NalLengthSize { get; set; }
public bool IsInterlaced { get; set; }
public bool IsAVC { get; set; }
public int BitRate { get; set; }
public int BitDepth { get; set; }
public int RefFrames { get; set; }
public bool IsDefault { get; set; }
public bool IsForced { get; set; }
public int Height { get; set; }
public int Width { get; set; }
public float AverageFrameRate { get; set; }
public float RealFrameRate { get; set; }
public string Profile { get; set; }
public string Type { get; set; }
public string AspectRatio { get; set; }
public int Index { get; set; }
public bool IsExternal { get; set; }
public bool IsTextSubtitleStream { get; set; }
public bool SupportsExternalStream { get; set; }
public string PixelFormat { get; set; }
public int Level { get; set; }
public bool IsAnamorphic { get; set; }
public string DisplayTitle { get; set; }
public string ChannelLayout { get; set; }
public int Channels { get; set; }
public int SampleRate { get; set; }
}
}

@ -0,0 +1,87 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyMovieInformation
{
public string Name { get; set; }
public string OriginalTitle { get; set; }
public string ServerId { get; set; }
public string Id { get; set; }
public string Etag { get; set; }
public DateTime DateCreated { get; set; }
public bool CanDelete { get; set; }
public bool CanDownload { get; set; }
public bool SupportsSync { get; set; }
public string Container { get; set; }
public string SortName { get; set; }
public DateTime PremiereDate { get; set; }
public EmbyExternalurl[] ExternalUrls { get; set; }
public EmbyMediasource[] MediaSources { get; set; }
public string[] ProductionLocations { get; set; }
public string Path { get; set; }
public string OfficialRating { get; set; }
public string Overview { get; set; }
public string[] Taglines { get; set; }
public string[] Genres { get; set; }
public float CommunityRating { get; set; }
public int VoteCount { get; set; }
public long RunTimeTicks { get; set; }
public string PlayAccess { get; set; }
public int ProductionYear { get; set; }
public bool IsPlaceHolder { get; set; }
public EmbyRemotetrailer[] RemoteTrailers { get; set; }
public EmbyProviderids ProviderIds { get; set; }
public bool IsHD { get; set; }
public bool IsFolder { get; set; }
public string ParentId { get; set; }
public string Type { get; set; }
public EmbyPerson[] People { get; set; }
public EmbyStudio[] Studios { get; set; }
public int LocalTrailerCount { get; set; }
public EmbyUserdata UserData { get; set; }
public string DisplayPreferencesId { get; set; }
public object[] Tags { get; set; }
public string[] Keywords { get; set; }
public EmbyMediastream[] MediaStreams { get; set; }
public string VideoType { get; set; }
public EmbyImagetags ImageTags { get; set; }
public string[] BackdropImageTags { get; set; }
public object[] ScreenshotImageTags { get; set; }
public EmbyChapter[] Chapters { get; set; }
public string LocationType { get; set; }
public string MediaType { get; set; }
public string HomePageUrl { get; set; }
public int Budget { get; set; }
public int Revenue { get; set; }
public object[] LockedFields { get; set; }
public bool LockData { get; set; }
}
}

@ -0,0 +1,59 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyMovieItem.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.Models.Emby
{
public class EmbyMovieItem
{
public string Name { get; set; }
public string ServerId { get; set; }
public string Id { get; set; }
public string Container { get; set; }
public DateTime PremiereDate { get; set; }
public object[] ProductionLocations { get; set; }
public string OfficialRating { get; set; }
public float CommunityRating { get; set; }
public long RunTimeTicks { get; set; }
public string PlayAccess { get; set; }
public int ProductionYear { get; set; }
public bool IsPlaceHolder { get; set; }
public bool IsHD { get; set; }
public bool IsFolder { get; set; }
public string Type { get; set; }
public int LocalTrailerCount { get; set; }
public EmbyUserdata UserData { get; set; }
public string VideoType { get; set; }
public EmbyImagetags ImageTags { get; set; }
public string[] BackdropImageTags { get; set; }
public string LocationType { get; set; }
public string MediaType { get; set; }
public bool HasSubtitles { get; set; }
public int CriticRating { get; set; }
}
}

@ -0,0 +1,39 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyPerson
{
public string Name { get; set; }
public string Id { get; set; }
public string Role { get; set; }
public string Type { get; set; }
public string PrimaryImageTag { get; set; }
}
}

@ -0,0 +1,63 @@
#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
namespace Ombi.Api.Models.Emby
{
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; }
}
}

@ -0,0 +1,41 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyProviderids
{
public string Tmdb { get; set; }
public string Imdb { get; set; }
public string TmdbCollection { get; set; }
public string Tvdb { get; set; }
public string Zap2It { get; set; }
public string TvRage { get; set; }
}
}

@ -0,0 +1,36 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyRemotetrailer
{
public string Url { get; set; }
public string Name { get; set; }
}
}

@ -0,0 +1,36 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyRequiredhttpheaders
{
}
}

@ -0,0 +1,83 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbySeriesInformation.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.Models.Emby
{
public class EmbySeriesInformation
{
public string Name { get; set; }
public string ServerId { get; set; }
public string Id { get; set; }
public string Etag { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateLastMediaAdded { get; set; }
public bool CanDelete { get; set; }
public bool CanDownload { get; set; }
public bool SupportsSync { get; set; }
public string SortName { get; set; }
public DateTime PremiereDate { get; set; }
public EmbyExternalurl[] ExternalUrls { get; set; }
public string Path { get; set; }
public string OfficialRating { get; set; }
public string Overview { get; set; }
public string ShortOverview { get; set; }
public object[] Taglines { get; set; }
public string[] Genres { get; set; }
public float CommunityRating { get; set; }
public int VoteCount { get; set; }
public long CumulativeRunTimeTicks { get; set; }
public long RunTimeTicks { get; set; }
public string PlayAccess { get; set; }
public int ProductionYear { get; set; }
public EmbyRemotetrailer[] RemoteTrailers { get; set; }
public EmbyProviderids ProviderIds { get; set; }
public bool IsFolder { get; set; }
public string ParentId { get; set; }
public string Type { get; set; }
public EmbyPerson[] People { get; set; }
public EmbyStudio[] Studios { get; set; }
public int LocalTrailerCount { get; set; }
public EmbyUserdata UserData { get; set; }
public int RecursiveItemCount { get; set; }
public int ChildCount { get; set; }
public string DisplayPreferencesId { get; set; }
public string Status { get; set; }
public string AirTime { get; set; }
public string[] AirDays { get; set; }
public object[] Tags { get; set; }
public object[] Keywords { get; set; }
public EmbyImagetags ImageTags { get; set; }
public string[] BackdropImageTags { get; set; }
public object[] ScreenshotImageTags { get; set; }
public string LocationType { get; set; }
public string HomePageUrl { get; set; }
public object[] LockedFields { get; set; }
public bool LockData { get; set; }
}
}

@ -0,0 +1,56 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbySeriesItem.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.Models.Emby
{
public class EmbySeriesItem
{
public string Name { get; set; }
public string ServerId { get; set; }
public string Id { get; set; }
public DateTime PremiereDate { get; set; }
public string OfficialRating { get; set; }
public float CommunityRating { get; set; }
public long RunTimeTicks { get; set; }
public string PlayAccess { get; set; }
public int ProductionYear { get; set; }
public bool IsFolder { get; set; }
public string Type { get; set; }
public int LocalTrailerCount { get; set; }
public EmbyUserdata UserData { get; set; }
public int ChildCount { get; set; }
public string Status { get; set; }
public string AirTime { get; set; }
public string[] AirDays { get; set; }
public EmbyImagetags ImageTags { get; set; }
public string[] BackdropImageTags { get; set; }
public string LocationType { get; set; }
public DateTime EndDate { get; set; }
}
}

@ -0,0 +1,37 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyEpisodeInformation.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.Models.Emby
{
public class EmbySeriesstudioinfo
{
public string Name { get; set; }
public string Id { get; set; }
}
}

@ -0,0 +1,37 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieInformation.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.Models.Emby
{
public class EmbyStudio
{
public string Name { get; set; }
public string Id { get; set; }
}
}

@ -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.Models.Emby
{
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; }
}
}

@ -0,0 +1,53 @@
#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.Models.Emby
{
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; }
}
public class EmbyUserLogin
{
public EmbyUser User { get; set; }
}
}

@ -0,0 +1,42 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyLibrary.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.Models.Emby
{
public class EmbyUserdata
{
public double PlaybackPositionTicks { get; set; }
public int PlayCount { get; set; }
public bool IsFavorite { get; set; }
public bool Played { get; set; }
public string Key { get; set; }
public DateTime LastPlayedDate { get; set; }
public int UnplayedItemCount { get; set; }
}
}

@ -49,6 +49,32 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Emby\EmbyChapter.cs" />
<Compile Include="Emby\EmbyConfiguration.cs" />
<Compile Include="Emby\EmbyEpisodeInformation.cs" />
<Compile Include="Emby\EmbyEpisodeItem.cs" />
<Compile Include="Emby\EmbyExternalurl.cs" />
<Compile Include="Emby\EmbyImagetags.cs" />
<Compile Include="Emby\EmbyInformation.cs" />
<Compile Include="Emby\EmbyItem.cs" />
<Compile Include="Emby\EmbyItemContainer.cs" />
<Compile Include="Emby\EmbyMediasource.cs" />
<Compile Include="Emby\EmbyMediastream.cs" />
<Compile Include="Emby\EmbyMediaType.cs" />
<Compile Include="Emby\EmbyMovieItem.cs" />
<Compile Include="Emby\EmbyPerson.cs" />
<Compile Include="Emby\EmbyPolicy.cs" />
<Compile Include="Emby\EmbyProviderids.cs" />
<Compile Include="Emby\EmbyRemotetrailer.cs" />
<Compile Include="Emby\EmbyRequiredhttpheaders.cs" />
<Compile Include="Emby\EmbySeriesInformation.cs" />
<Compile Include="Emby\EmbySeriesItem.cs" />
<Compile Include="Emby\EmbySeriesstudioinfo.cs" />
<Compile Include="Emby\EmbyStudio.cs" />
<Compile Include="Emby\EmbySystemInfo.cs" />
<Compile Include="Emby\EmbyUser.cs" />
<Compile Include="Emby\EmbyUserdata.cs" />
<Compile Include="Emby\EmbyMovieInformation.cs" />
<Compile Include="Movie\CouchPotatoAdd.cs" /> <Compile Include="Movie\CouchPotatoAdd.cs" />
<Compile Include="Movie\CouchPotatoMovies.cs" /> <Compile Include="Movie\CouchPotatoMovies.cs" />
<Compile Include="Movie\CouchPotatoProfiles.cs" /> <Compile Include="Movie\CouchPotatoProfiles.cs" />

@ -49,13 +49,6 @@ namespace Ombi.Api.Models.Watcher
public string requiredwords { get; set; } public string requiredwords { get; set; }
} }
public class Quality
{
[JsonProperty("Quality")]
public Quality2 quality { get; set; }
public Filters Filters { get; set; }
}
public class WatcherListStatusResult public class WatcherListStatusResult
{ {
public string status { get; set; } public string status { get; set; }
@ -72,7 +65,7 @@ namespace Ombi.Api.Models.Watcher
public string dvd { get; set; } public string dvd { get; set; }
public string tomatorating { get; set; } public string tomatorating { get; set; }
public string imdbid { get; set; } public string imdbid { get; set; }
public Quality quality { get; set; } public string quality { get; set; }
} }

@ -27,6 +27,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Net;
using System.Xml.Serialization; using System.Xml.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog; using NLog;
@ -76,14 +77,7 @@ namespace Ombi.Api
var client = new RestClient { BaseUrl = baseUri }; var client = new RestClient { BaseUrl = baseUri };
var response = client.Execute(request); var response = client.Execute(request);
if (response.ErrorException != null)
{
Log.Error(response.ErrorException);
var message = "Error retrieving response. Check inner details for more info.";
throw new ApiRequestException(message, response.ErrorException);
}
return response; return response;
} }

@ -100,9 +100,9 @@ namespace Ombi.Api
var obj = RetryHandler.Execute<CouchPotatoStatus>(() => Api.Execute<CouchPotatoStatus>(request, url), var obj = RetryHandler.Execute<CouchPotatoStatus>(() => Api.Execute<CouchPotatoStatus>(request, url),
(exception, timespan) => Log.Error(exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan), new TimeSpan[] { (exception, timespan) => Log.Error(exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan), new TimeSpan[] {
TimeSpan.FromSeconds (2), TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10)}); TimeSpan.FromSeconds(3)});
return obj; return obj;
} }
@ -140,9 +140,9 @@ namespace Ombi.Api
{ {
var obj = RetryHandler.Execute(() => Api.Execute<CouchPotatoMovies>(request, baseUrl), var obj = RetryHandler.Execute(() => Api.Execute<CouchPotatoMovies>(request, baseUrl),
(exception, timespan) => Log.Error(exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan), new[] { (exception, timespan) => Log.Error(exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan), new[] {
TimeSpan.FromSeconds (5), TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(30) TimeSpan.FromSeconds(5)
}); });
return obj; return obj;

@ -0,0 +1,314 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyApi.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.Net;
using Newtonsoft.Json;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Helpers;
using Polly;
using RestSharp;
namespace Ombi.Api
{
public class EmbyApi : IEmbyApi
{
public EmbyApi()
{
Api = new ApiRequest();
}
private ApiRequest Api { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
/// <summary>
/// Returns all users from the Emby Instance
/// </summary>
/// <param name="baseUri"></param>
/// <param name="apiKey"></param>
public List<EmbyUser> GetUsers(Uri baseUri, string apiKey)
{
var request = new RestRequest
{
Resource = "emby/users",
Method = Method.GET
};
AddHeaders(request, apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetUsers for Emby, Retrying {0}", timespan), new[] {
TimeSpan.FromSeconds (1),
});
var obj = policy.Execute(() => Api.ExecuteJson<List<EmbyUser>>(request, baseUri));
return obj;
}
public EmbySystemInfo GetSystemInformation(string apiKey, Uri baseUrl)
{
var request = new RestRequest
{
Resource = "emby/System/Info",
Method = Method.GET
};
AddHeaders(request, apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetSystemInformation for Emby, Retrying {0}", timespan), new[] {
TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(5)
});
var obj = policy.Execute(() => Api.ExecuteJson<EmbySystemInfo>(request, baseUrl));
return obj;
}
public EmbyItemContainer<EmbyLibrary> ViewLibrary(string apiKey, string userId, Uri baseUri)
{
var request = new RestRequest
{
Resource = "emby/users/{userId}/items",
Method = Method.GET
};
request.AddUrlSegment("userId", userId);
AddHeaders(request, apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling ViewLibrary for Emby, Retrying {0}", timespan), new[] {
TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(5)
});
var obj = policy.Execute(() => Api.ExecuteJson<EmbyItemContainer<EmbyLibrary>>(request, baseUri));
return obj;
}
public EmbyItemContainer<EmbyMovieItem> GetAllMovies(string apiKey, string userId, Uri baseUri)
{
return GetAll<EmbyMovieItem>("Movie", apiKey, userId, baseUri);
}
public EmbyItemContainer<EmbyEpisodeItem> GetAllEpisodes(string apiKey, string userId, Uri baseUri)
{
return GetAll<EmbyEpisodeItem>("Episode", apiKey, userId, baseUri);
}
public EmbyItemContainer<EmbyMovieInformation> GetCollection(string mediaId, string apiKey, string userId, Uri baseUrl)
{
var request = new RestRequest
{
Resource = "emby/users/{userId}/items?parentId={mediaId}",
Method = Method.GET
};
request.AddUrlSegment("userId", userId);
request.AddUrlSegment("mediaId", mediaId);
AddHeaders(request, apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetCollections for Emby, Retrying {0}", timespan), new[] {
TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(5)
});
return policy.Execute(() => Api.ExecuteJson<EmbyItemContainer<EmbyMovieInformation>>(request, baseUrl));
}
public EmbyInformation GetInformation(string mediaId, EmbyMediaType type, string apiKey, string userId, Uri baseUri)
{
var request = new RestRequest
{
Resource = "emby/users/{userId}/items/{mediaId}",
Method = Method.GET
};
request.AddUrlSegment("userId", userId);
request.AddUrlSegment("mediaId", mediaId);
AddHeaders(request, apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetAll<T>({1}) for Emby, Retrying {0}", timespan, type), new[] {
TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(5)
});
IRestResponse response = null;
try
{
switch (type)
{
case EmbyMediaType.Movie:
response = policy.Execute(() => Api.Execute(request, baseUri));
break;
case EmbyMediaType.Series:
response = policy.Execute(() => Api.Execute(request, baseUri));
break;
case EmbyMediaType.Music:
break;
case EmbyMediaType.Episode:
response = policy.Execute(() => Api.Execute(request, baseUri));
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
var info = new EmbyInformation();
switch (type)
{
case EmbyMediaType.Movie:
return new EmbyInformation
{
MovieInformation = JsonConvert.DeserializeObject<EmbyMovieInformation>(response.Content)
};
case EmbyMediaType.Series:
return new EmbyInformation
{
SeriesInformation = JsonConvert.DeserializeObject<EmbySeriesInformation>(response.Content)
};
case EmbyMediaType.Music:
break;
case EmbyMediaType.Episode:
return new EmbyInformation
{
EpisodeInformation = JsonConvert.DeserializeObject<EmbyEpisodeInformation>(response.Content)
};
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
catch (Exception e)
{
Log.Error("Could not get the media item's information");
Log.Error(e);
Log.Debug("ResponseContent");
Log.Debug(response?.Content ?? "Empty");
Log.Debug("ResponseStatusCode");
Log.Debug(response?.StatusCode ?? HttpStatusCode.PreconditionFailed);
Log.Debug("ResponseError");
Log.Debug(response?.ErrorMessage ?? "No Error");
Log.Debug("ResponseException");
Log.Debug(response?.ErrorException ?? new Exception());
throw;
}
return new EmbyInformation();
}
public EmbyItemContainer<EmbySeriesItem> GetAllShows(string apiKey, string userId, Uri baseUri)
{
return GetAll<EmbySeriesItem>("Series", apiKey, userId, baseUri);
}
public EmbyUser LogIn(string username, string password, string apiKey, Uri baseUri)
{
var request = new RestRequest
{
Resource = "emby/users/authenticatebyname",
Method = Method.POST
};
var body = new
{
username,
password = StringHasher.GetSha1Hash(password).ToLower(),
passwordMd5 = StringHasher.CalcuateMd5Hash(password)
};
request.AddJsonBody(body);
request.AddHeader("X-Emby-Authorization",
$"MediaBrowser Client=\"Ombi\", Device=\"Ombi\", DeviceId=\"{AssemblyHelper.GetProductVersion()}\", Version=\"{AssemblyHelper.GetAssemblyVersion()}\"");
AddHeaders(request, apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling LogInfor Emby, Retrying {0}", timespan), new[] {
TimeSpan.FromSeconds (1)
});
var obj = policy.Execute(() => Api.Execute(request, baseUri));
if (obj.StatusCode == HttpStatusCode.Unauthorized)
{
return null;
}
return JsonConvert.DeserializeObject<EmbyUserLogin>(obj.Content)?.User;
}
private EmbyItemContainer<T> GetAll<T>(string type, string apiKey, string userId, Uri baseUri)
{
var request = new RestRequest
{
Resource = "emby/users/{userId}/items",
Method = Method.GET
};
request.AddUrlSegment("userId", userId);
request.AddQueryParameter("Recursive", true.ToString());
request.AddQueryParameter("IncludeItemTypes", type);
AddHeaders(request, apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetAll<T>({1}) for Emby, Retrying {0}", timespan, type), new[] {
TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(5)
});
var obj = policy.Execute(() => Api.ExecuteJson<EmbyItemContainer<T>>(request, baseUri));
return obj;
}
private static void AddHeaders(IRestRequest req, string apiKey)
{
if (!string.IsNullOrEmpty(apiKey))
{
req.AddHeader("X-MediaBrowser-Token", apiKey);
}
req.AddHeader("Accept", "application/json");
req.AddHeader("Content-Type", "application/json");
req.AddHeader("Device", "Ombi");
}
}
}

@ -70,10 +70,14 @@
<HintPath>..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll</HintPath> <HintPath>..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="WebSocket4Net, Version=0.14.1.0, Culture=neutral, PublicKeyToken=eb4e154b696bf72a, processorArchitecture=MSIL">
<HintPath>..\packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ApiRequest.cs" /> <Compile Include="ApiRequest.cs" />
<Compile Include="DiscordApi.cs" /> <Compile Include="DiscordApi.cs" />
<Compile Include="EmbyApi.cs" />
<Compile Include="NetflixRouletteApi.cs" /> <Compile Include="NetflixRouletteApi.cs" />
<Compile Include="RadarrApi.cs" /> <Compile Include="RadarrApi.cs" />
<Compile Include="TraktApi.cs" /> <Compile Include="TraktApi.cs" />

@ -62,6 +62,20 @@ namespace Ombi.Api
return obj; return obj;
} }
public List<SonarrRootFolder> GetRootFolders(string apiKey, Uri baseUrl)
{
var request = new RestRequest { Resource = "/api/rootfolder", Method = Method.GET };
request.AddHeader("X-Api-Key", apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetRootFolders for Radarr, Retrying {0}", timespan), new TimeSpan[] {
TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(2)
});
var obj = policy.Execute(() => Api.ExecuteJson<List<SonarrRootFolder>>(request, baseUrl));
return obj;
}
public RadarrAddMovie AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, Uri baseUrl, bool searchNow = false) public RadarrAddMovie AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, Uri baseUrl, bool searchNow = false)
{ {

@ -52,9 +52,9 @@ namespace Ombi.Api
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetProfiles for Sonarr, Retrying {0}", timespan), new TimeSpan[] { var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetProfiles for Sonarr, Retrying {0}", timespan), new TimeSpan[] {
TimeSpan.FromSeconds (2), TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10) TimeSpan.FromSeconds(5)
}); });
var obj = policy.Execute(() => Api.ExecuteJson<List<SonarrProfile>>(request, baseUrl)); var obj = policy.Execute(() => Api.ExecuteJson<List<SonarrProfile>>(request, baseUrl));
@ -68,9 +68,9 @@ namespace Ombi.Api
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetRootFolders for Sonarr, Retrying {0}", timespan), new TimeSpan[] { var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetRootFolders for Sonarr, Retrying {0}", timespan), new TimeSpan[] {
TimeSpan.FromSeconds (2), TimeSpan.FromSeconds (1),
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10) TimeSpan.FromSeconds(5)
}); });
var obj = policy.Execute(() => Api.ExecuteJson<List<SonarrRootFolder>>(request, baseUrl)); var obj = policy.Execute(() => Api.ExecuteJson<List<SonarrRootFolder>>(request, baseUrl));

@ -28,6 +28,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using NLog; using NLog;
using Ombi.Api.Models.Tv; using Ombi.Api.Models.Tv;
using RestSharp; using RestSharp;
@ -90,21 +91,29 @@ namespace Ombi.Api
}; };
request.AddUrlSegment("id", theTvDbId.ToString()); request.AddUrlSegment("id", theTvDbId.ToString());
request.AddHeader("Content-Type", "application/json"); request.AddHeader("Content-Type", "application/json");
try
{
var result = Api.Execute(request, new Uri(Uri));
var obj = JsonConvert.DeserializeObject<TvMazeShow>(result.Content);
var obj = Api.Execute<TvMazeShow>(request, new Uri(Uri)); var episodes = EpisodeLookup(obj.id).ToList();
var episodes = EpisodeLookup(obj.id).ToList();
foreach (var e in episodes) foreach (var e in episodes)
{
obj.Season.Add(new TvMazeCustomSeason
{ {
SeasonNumber = e.season, obj.Season.Add(new TvMazeCustomSeason
EpisodeNumber = e.number {
}); SeasonNumber = e.season,
EpisodeNumber = e.number
});
}
return obj;
} }
catch (Exception e)
return obj; {
Log.Error(e);
return null;
}
} }
public List<TvMazeSeasons> GetSeasons(int id) public List<TvMazeSeasons> GetSeasons(int id)

@ -9,4 +9,5 @@
<package id="System.Net.Http" version="4.0.0" targetFramework="net45" /> <package id="System.Net.Http" version="4.0.0" targetFramework="net45" />
<package id="TMDbLib" version="0.9.0.0-alpha" targetFramework="net45" /> <package id="TMDbLib" version="0.9.0.0-alpha" targetFramework="net45" />
<package id="TraktApiSharp" version="0.8.0" targetFramework="net45" /> <package id="TraktApiSharp" version="0.8.0" targetFramework="net45" />
<package id="WebSocket4Net" version="0.14.1" targetFramework="net45" />
</packages> </packages>

@ -37,6 +37,7 @@ using Ombi.Helpers;
using Ombi.Helpers.Permissions; using Ombi.Helpers.Permissions;
using Ombi.Store; using Ombi.Store;
using Ombi.Store.Models; using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository; using Ombi.Store.Repository;
namespace Ombi.Core.Migration.Migrations namespace Ombi.Core.Migration.Migrations
@ -46,7 +47,7 @@ namespace Ombi.Core.Migration.Migrations
{ {
public Version1100(IUserRepository userRepo, IRequestService requestService, ISettingsService<LogSettings> log, public Version1100(IUserRepository userRepo, IRequestService requestService, ISettingsService<LogSettings> log,
IPlexApi plexApi, ISettingsService<PlexSettings> plexService, IPlexApi plexApi, ISettingsService<PlexSettings> plexService,
IPlexUserRepository plexusers, ISettingsService<PlexRequestSettings> prSettings, IExternalUserRepository<PlexUsers> plexusers, ISettingsService<PlexRequestSettings> prSettings,
ISettingsService<UserManagementSettings> umSettings, ISettingsService<UserManagementSettings> umSettings,
ISettingsService<ScheduledJobsSettings> sjs, IRepository<UsersToNotify> usersToNotify) ISettingsService<ScheduledJobsSettings> sjs, IRepository<UsersToNotify> usersToNotify)
{ {
@ -69,7 +70,7 @@ namespace Ombi.Core.Migration.Migrations
private ISettingsService<LogSettings> Log { get; } private ISettingsService<LogSettings> Log { get; }
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
private IPlexUserRepository PlexUsers { get; } private IExternalUserRepository<PlexUsers> PlexUsers { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; } private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings { get; } private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
private ISettingsService<ScheduledJobsSettings> ScheduledJobSettings { get; } private ISettingsService<ScheduledJobsSettings> ScheduledJobSettings { get; }
@ -180,7 +181,7 @@ namespace Ombi.Core.Migration.Migrations
try try
{ {
var settings = PlexSettings.GetSettings(); var settings = PlexSettings.GetSettings();
if (string.IsNullOrEmpty(settings.PlexAuthToken)) if (string.IsNullOrEmpty(settings.PlexAuthToken) || !settings.Enable)
{ {
return; return;
} }

@ -62,6 +62,7 @@ namespace Ombi.Core.Migration.Migrations
private void UpdateApplicationSettings() private void UpdateApplicationSettings()
{ {
var plex = PlexRequestSettings.GetSettings(); var plex = PlexRequestSettings.GetSettings();
var jobSettings = Jobs.GetSettings(); var jobSettings = Jobs.GetSettings();
var newsLetter = NewsletterSettings.GetSettings(); var newsLetter = NewsletterSettings.GetSettings();

@ -30,35 +30,59 @@
using System.Data; using System.Data;
using NLog; using NLog;
using Ombi.Core.SettingModels; using Ombi.Core.SettingModels;
using Ombi.Store;
namespace Ombi.Core.Migration.Migrations namespace Ombi.Core.Migration.Migrations
{ {
[Migration(22000, "v2.20.0.0")] [Migration(22000, "v2.20.0.0")]
public class Version2200 : BaseMigration, IMigration public class Version2200 : BaseMigration, IMigration
{ {
public Version2200(ISettingsService<CustomizationSettings> custom) public Version2200(ISettingsService<CustomizationSettings> custom, ISettingsService<PlexSettings> ps)
{ {
Customization = custom; Customization = custom;
PlexSettings = ps;
} }
public int Version => 22000; public int Version => 22000;
private ISettingsService<CustomizationSettings> Customization { get; set; } private ISettingsService<CustomizationSettings> Customization { get; set; }
private ISettingsService<PlexSettings> PlexSettings { get; set; }
private static Logger Logger = LogManager.GetCurrentClassLogger(); private static Logger Logger = LogManager.GetCurrentClassLogger();
public void Start(IDbConnection con) public void Start(IDbConnection con)
{ {
//UpdateCustomSettings(); Turned off the migration for now until the search has been improved on. UpdatePlexSettings();
//UpdateSchema(con, Version); UpdateCustomSettings();
AddNewColumns(con);
UpdateSchema(con, Version);
} }
private void AddNewColumns(IDbConnection con)
{
con.AlterTable("EmbyContent", "ADD", "AddedAt", true, "VARCHAR(50)");
con.AlterTable("EmbyEpisodes", "ADD", "AddedAt", true, "VARCHAR(50)");
}
private void UpdatePlexSettings()
{
#if !DEBUG
var s = PlexSettings.GetSettings();
if (!string.IsNullOrEmpty(s.Ip))
{
s.Enable = true;
PlexSettings.SaveSettings(s);
}
#endif
}
private void UpdateCustomSettings() private void UpdateCustomSettings()
{ {
var settings = Customization.GetSettings(); var settings = Customization.GetSettings();
settings.NewSearch = true; // Use the new search settings.EnableIssues = true;
Customization.SaveSettings(settings); Customization.SaveSettings(settings);
} }
} }
} }

@ -0,0 +1,169 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieSenderTests.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Radarr;
using Ombi.Api.Models.Sonarr;
using Ombi.Api.Models.Watcher;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Store;
using Ploeh.AutoFixture;
namespace Ombi.Core.Tests
{
public class MovieSenderTests
{
private MovieSender Sender { get; set; }
private Mock<ISettingsService<CouchPotatoSettings>> CpMock { get; set; }
private Mock<ISettingsService<WatcherSettings>> WatcherMock { get; set; }
private Mock<ISettingsService<RadarrSettings>> RadarrMock { get; set; }
private Mock<ICouchPotatoApi> CpApiMock { get; set; }
private Mock<IWatcherApi> WatcherApiMock { get; set; }
private Mock<IRadarrApi> RadarrApiMock { get; set; }
private Mock<ICacheProvider> CacheMock { get; set; }
private Fixture F { get; set; }
[SetUp]
public void Setup()
{
F = new Fixture();
CpMock = new Mock<ISettingsService<CouchPotatoSettings>>();
WatcherMock = new Mock<ISettingsService<WatcherSettings>>();
RadarrApiMock = new Mock<IRadarrApi>();
RadarrMock = new Mock<ISettingsService<RadarrSettings>>();
CpApiMock = new Mock<ICouchPotatoApi>();
WatcherApiMock = new Mock<IWatcherApi>();
CacheMock = new Mock<ICacheProvider>();
RadarrMock.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(F.Build<RadarrSettings>().With(x => x.Enabled, false).Create());
WatcherMock.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(F.Build<WatcherSettings>().With(x => x.Enabled, false).Create());
CpMock.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(F.Build<CouchPotatoSettings>().With(x => x.Enabled, false).Create());
Sender = new MovieSender(CpMock.Object, WatcherMock.Object, CpApiMock.Object, WatcherApiMock.Object, RadarrApiMock.Object, RadarrMock.Object, CacheMock.Object);
}
[Test]
public async Task SendRadarrMovie()
{
RadarrMock.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(F.Build<RadarrSettings>().With(x => x.Enabled, true).Create());
RadarrApiMock.Setup(x => x.AddMovie(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<Uri>(), It.IsAny<bool>())).Returns(new RadarrAddMovie { title = "Abc" });
CacheMock.Setup(x => x.GetOrSet<List<SonarrRootFolder>>(CacheKeys.RadarrRootFolders, It.IsAny<Func<List<SonarrRootFolder>>>(), It.IsAny<int>()))
.Returns(F.CreateMany<SonarrRootFolder>().ToList());
var model = F.Create<RequestedModel>();
var result = await Sender.Send(model, 2.ToString());
Assert.That(result.Result, Is.True);
Assert.That(result.Error, Is.False);
Assert.That(result.MovieSendingEnabled, Is.True);
RadarrApiMock.Verify(x => x.AddMovie(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<int>(), 2, It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<Uri>(), It.IsAny<bool>()), Times.Once);
}
[Test]
public async Task SendRadarrMovie_SendingFailed()
{
RadarrMock.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(F.Build<RadarrSettings>().With(x => x.Enabled, true).Create());
RadarrApiMock.Setup(x => x.AddMovie(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<Uri>(), It.IsAny<bool>())).Returns(new RadarrAddMovie { Error = new RadarrError{message = "Movie Already Added"}});
CacheMock.Setup(x => x.GetOrSet<List<SonarrRootFolder>>(CacheKeys.RadarrRootFolders, It.IsAny<Func<List<SonarrRootFolder>>>(), It.IsAny<int>()))
.Returns(F.CreateMany<SonarrRootFolder>().ToList());
var model = F.Create<RequestedModel>();
var result = await Sender.Send(model, 2.ToString());
Assert.That(result.Result, Is.False);
Assert.That(result.Error, Is.True);
Assert.That(result.MovieSendingEnabled, Is.True);
RadarrApiMock.Verify(x => x.AddMovie(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<int>(), 2, It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<Uri>(), It.IsAny<bool>()), Times.Once);
}
[Test]
public async Task SendCpMovie()
{
CpMock.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(F.Build<CouchPotatoSettings>().With(x => x.Enabled, true).Create());
CpApiMock.Setup(x => x.AddMovie(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<Uri>(), It.IsAny<string>())).Returns(true);
var model = F.Create<RequestedModel>();
var result = await Sender.Send(model);
Assert.That(result.Result, Is.True);
Assert.That(result.Error, Is.False);
Assert.That(result.MovieSendingEnabled, Is.True);
CpApiMock.Verify(x => x.AddMovie(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<Uri>(), It.IsAny<string>()), Times.Once);
}
[Test]
public async Task SendWatcherMovie()
{
WatcherMock.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(F.Build<WatcherSettings>().With(x => x.Enabled, true).Create());
WatcherApiMock.Setup(x => x.AddMovie(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(F.Create<WatcherAddMovieResult>());
var model = F.Create<RequestedModel>();
var result = await Sender.Send(model);
Assert.That(result.Result, Is.True);
Assert.That(result.Error, Is.False);
Assert.That(result.MovieSendingEnabled, Is.True);
WatcherApiMock.Verify(x => x.AddMovie(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>()), Times.Once);
}
}
}

@ -60,6 +60,7 @@
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="AuthenticationSettingsTests.cs" /> <Compile Include="AuthenticationSettingsTests.cs" />
<Compile Include="MovieSenderTests.cs" />
<Compile Include="NotificationMessageResolverTests.cs" /> <Compile Include="NotificationMessageResolverTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
@ -68,6 +69,18 @@
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ombi.Api.Interfaces\Ombi.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
<Name>Ombi.Api.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\Ombi.Api.Models\Ombi.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>Ombi.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj">
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
<Name>Ombi.Api</Name>
</ProjectReference>
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj"> <ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj">
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project> <Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>Ombi.Core</Name> <Name>Ombi.Core</Name>
@ -76,6 +89,10 @@
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project> <Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>Ombi.Helpers</Name> <Name>Ombi.Helpers</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj">
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
<Name>Ombi.Store</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Choose> <Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">

@ -45,8 +45,11 @@ namespace Ombi.Core
public const string CouchPotatoQualityProfiles = nameof(CouchPotatoQualityProfiles); public const string CouchPotatoQualityProfiles = nameof(CouchPotatoQualityProfiles);
public const string CouchPotatoQueued = nameof(CouchPotatoQueued); public const string CouchPotatoQueued = nameof(CouchPotatoQueued);
public const string WatcherQueued = nameof(WatcherQueued); public const string WatcherQueued = nameof(WatcherQueued);
public const string GetCustomizationSettings = nameof(GetCustomizationSettings);
public const string GetEmbySettings = nameof(GetEmbySettings);
public const string GetPlexRequestSettings = nameof(GetPlexRequestSettings); public const string GetPlexRequestSettings = nameof(GetPlexRequestSettings);
public const string LastestProductVersion = nameof(LastestProductVersion); public const string LastestProductVersion = nameof(LastestProductVersion);
public const string SonarrRootFolders = nameof(SonarrRootFolders); public const string SonarrRootFolders = nameof(SonarrRootFolders);
public const string RadarrRootFolders = nameof(RadarrRootFolders);
} }
} }

@ -22,7 +22,7 @@ namespace Ombi.Core
Func<NancyContext, Response> HttpStatusCodeIfNot(HttpStatusCode statusCode, Func<NancyContext, bool> test); Func<NancyContext, Response> HttpStatusCodeIfNot(HttpStatusCode statusCode, Func<NancyContext, bool> test);
bool IsLoggedIn(NancyContext context); bool IsLoggedIn(NancyContext context);
bool IsNormalUser(IUserIdentity user); bool IsNormalUser(IUserIdentity user);
bool IsPlexUser(IUserIdentity user); bool IsExternalUser(IUserIdentity user);
bool HasPermissions(string userName, Permissions perm); bool HasPermissions(string userName, Permissions perm);
/// <summary> /// <summary>

@ -26,10 +26,12 @@
#endregion #endregion
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using Ombi.Api.Interfaces; using Ombi.Api.Interfaces;
using Ombi.Core.SettingModels; using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Store; using Ombi.Store;
namespace Ombi.Core namespace Ombi.Core
@ -37,7 +39,8 @@ namespace Ombi.Core
public class MovieSender : IMovieSender public class MovieSender : IMovieSender
{ {
public MovieSender(ISettingsService<CouchPotatoSettings> cp, ISettingsService<WatcherSettings> watcher, public MovieSender(ISettingsService<CouchPotatoSettings> cp, ISettingsService<WatcherSettings> watcher,
ICouchPotatoApi cpApi, IWatcherApi watcherApi, IRadarrApi radarrApi, ISettingsService<RadarrSettings> radarrSettings) ICouchPotatoApi cpApi, IWatcherApi watcherApi, IRadarrApi radarrApi, ISettingsService<RadarrSettings> radarrSettings,
ICacheProvider cache)
{ {
CouchPotatoSettings = cp; CouchPotatoSettings = cp;
WatcherSettings = watcher; WatcherSettings = watcher;
@ -45,6 +48,7 @@ namespace Ombi.Core
WatcherApi = watcherApi; WatcherApi = watcherApi;
RadarrSettings = radarrSettings; RadarrSettings = radarrSettings;
RadarrApi = radarrApi; RadarrApi = radarrApi;
Cache = cache;
} }
private ISettingsService<CouchPotatoSettings> CouchPotatoSettings { get; } private ISettingsService<CouchPotatoSettings> CouchPotatoSettings { get; }
@ -53,6 +57,7 @@ namespace Ombi.Core
private IRadarrApi RadarrApi { get; } private IRadarrApi RadarrApi { get; }
private ICouchPotatoApi CpApi { get; } private ICouchPotatoApi CpApi { get; }
private IWatcherApi WatcherApi { get; } private IWatcherApi WatcherApi { get; }
private ICacheProvider Cache { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
public async Task<MovieSenderResult> Send(RequestedModel model, string qualityId = "") public async Task<MovieSenderResult> Send(RequestedModel model, string qualityId = "")
@ -73,7 +78,7 @@ namespace Ombi.Core
if (radarrSettings.Enabled) if (radarrSettings.Enabled)
{ {
return SendToRadarr(model, radarrSettings); return SendToRadarr(model, radarrSettings, qualityId);
} }
return new MovieSenderResult { Result = false, MovieSendingEnabled = false }; return new MovieSenderResult { Result = false, MovieSendingEnabled = false };
@ -102,16 +107,26 @@ namespace Ombi.Core
return new MovieSenderResult { Result = result, MovieSendingEnabled = true }; return new MovieSenderResult { Result = result, MovieSendingEnabled = true };
} }
private MovieSenderResult SendToRadarr(RequestedModel model, RadarrSettings settings) private MovieSenderResult SendToRadarr(RequestedModel model, RadarrSettings settings, string qualityId)
{ {
var qualityProfile = 0; var qualityProfile = 0;
int.TryParse(settings.QualityProfile, out qualityProfile); if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality
var result = RadarrApi.AddMovie(model.ProviderId, model.Title, model.ReleaseDate.Year, qualityProfile, settings.RootPath, settings.ApiKey, settings.FullUri, true); {
int.TryParse(qualityId, out qualityProfile);
}
if (qualityProfile <= 0)
{
int.TryParse(settings.QualityProfile, out qualityProfile);
}
var rootFolderPath = model.RootFolderSelected <= 0 ? settings.FullRootPath : GetRootPath(model.RootFolderSelected, settings);
var result = RadarrApi.AddMovie(model.ProviderId, model.Title, model.ReleaseDate.Year, qualityProfile, rootFolderPath, settings.ApiKey, settings.FullUri, true);
if (!string.IsNullOrEmpty(result.Error?.message)) if (!string.IsNullOrEmpty(result.Error?.message))
{ {
Log.Error(result.Error.message); Log.Error(result.Error.message);
return new MovieSenderResult { Result = false, Error = true}; return new MovieSenderResult { Result = false, Error = true , MovieSendingEnabled = true};
} }
if (!string.IsNullOrEmpty(result.title)) if (!string.IsNullOrEmpty(result.title))
{ {
@ -119,5 +134,16 @@ namespace Ombi.Core
} }
return new MovieSenderResult { Result = false, MovieSendingEnabled = true }; return new MovieSenderResult { Result = false, MovieSendingEnabled = true };
} }
private string GetRootPath(int pathId, RadarrSettings sonarrSettings)
{
var rootFoldersResult = Cache.GetOrSet(CacheKeys.RadarrRootFolders, () => RadarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri));
foreach (var r in rootFoldersResult.Where(r => r.id == pathId))
{
return r.path;
}
return string.Empty;
}
} }
} }

@ -144,7 +144,7 @@
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%"> <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr> <tr>
<td align="center"> <td align="center">
<img src="http://i.imgur.com/ROTp8mn.png" text-align="center" /> <img src="http://i.imgur.com/qQsN78U.png" width="400px" text-align="center" />
</td> </td>
</tr> </tr>
<tr> <tr>

@ -123,6 +123,8 @@
<Compile Include="SecurityExtensions.cs" /> <Compile Include="SecurityExtensions.cs" />
<Compile Include="SettingModels\AuthenticationSettings.cs" /> <Compile Include="SettingModels\AuthenticationSettings.cs" />
<Compile Include="SettingModels\DiscordNotificationSettings.cs" /> <Compile Include="SettingModels\DiscordNotificationSettings.cs" />
<Compile Include="SettingModels\EmbySettings.cs" />
<Compile Include="SettingModels\MassEmailSettings.cs" />
<Compile Include="SettingModels\RadarrSettings.cs" /> <Compile Include="SettingModels\RadarrSettings.cs" />
<Compile Include="SettingModels\WatcherSettings.cs" /> <Compile Include="SettingModels\WatcherSettings.cs" />
<Compile Include="SettingModels\ExternalSettings.cs" /> <Compile Include="SettingModels\ExternalSettings.cs" />

@ -105,7 +105,7 @@ namespace Ombi.Core.Queue
public IEnumerable<RequestQueue> GetQueue() public IEnumerable<RequestQueue> GetQueue()
{ {
var items = RequestQueue.GetAll(); var items = RequestQueue.GetAll();
return items; return items;
} }

@ -36,23 +36,28 @@ using Ombi.Core.SettingModels;
using Ombi.Core.Users; using Ombi.Core.Users;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Helpers.Permissions; using Ombi.Helpers.Permissions;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository; using Ombi.Store.Repository;
namespace Ombi.Core namespace Ombi.Core
{ {
public class SecurityExtensions : ISecurityExtensions public class SecurityExtensions : ISecurityExtensions
{ {
public SecurityExtensions(IUserRepository userRepository, IResourceLinker linker, IPlexUserRepository plexUsers, ISettingsService<UserManagementSettings> umSettings) public SecurityExtensions(IUserRepository userRepository, IResourceLinker linker, IExternalUserRepository<PlexUsers> plexUsers, ISettingsService<UserManagementSettings> umSettings,
IExternalUserRepository<EmbyUsers> embyUsers)
{ {
UserRepository = userRepository; UserRepository = userRepository;
Linker = linker; Linker = linker;
PlexUsers = plexUsers; PlexUsers = plexUsers;
UserManagementSettings = umSettings; UserManagementSettings = umSettings;
EmbyUsers = embyUsers;
} }
private IUserRepository UserRepository { get; } private IUserRepository UserRepository { get; }
private IResourceLinker Linker { get; } private IResourceLinker Linker { get; }
private IPlexUserRepository PlexUsers { get; } private IExternalUserRepository<PlexUsers> PlexUsers { get; }
private IExternalUserRepository<EmbyUsers> EmbyUsers { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings { get; } private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
public bool IsLoggedIn(NancyContext context) public bool IsLoggedIn(NancyContext context)
@ -69,16 +74,18 @@ namespace Ombi.Core
return realUser || plexUser; return realUser || plexUser;
} }
public bool IsPlexUser(IUserIdentity user) public bool IsExternalUser(IUserIdentity user)
{ {
if (user == null) if (user == null)
{ {
return false; return false;
} }
var plexUser = PlexUsers.GetUserByUsername(user.UserName); var plexUser = PlexUsers.GetUserByUsername(user.UserName);
return plexUser != null; var embyUser = EmbyUsers.GetUserByUsername(user.UserName);
}
return plexUser != null || embyUser != null;
}
public bool IsNormalUser(IUserIdentity user) public bool IsNormalUser(IUserIdentity user)
{ {
if (user == null) if (user == null)
@ -106,6 +113,12 @@ namespace Ombi.Core
return !string.IsNullOrEmpty(plexUser.UserAlias) ? plexUser.UserAlias : plexUser.Username; return !string.IsNullOrEmpty(plexUser.UserAlias) ? plexUser.UserAlias : plexUser.Username;
} }
var embyUser = EmbyUsers.GetUserByUsername(username);
if (embyUser != null)
{
return !string.IsNullOrEmpty(embyUser.UserAlias) ? embyUser.UserAlias : embyUser.Username;
}
var dbUser = UserRepository.GetUserByUsername(username); var dbUser = UserRepository.GetUserByUsername(username);
if (dbUser != null) if (dbUser != null)
{ {
@ -302,6 +315,12 @@ namespace Ombi.Core
return permissions; return permissions;
} }
var embyUsers = EmbyUsers.GetUserByUsername(userName);
if (embyUsers != null)
{
return (Permissions) embyUsers.Permissions;
}
return 0; return 0;
} }
} }

@ -54,6 +54,7 @@ namespace Ombi.Core.SettingModels
public int DefaultLang { get; set; } public int DefaultLang { get; set; }
public bool NewSearch { get; set; } public bool NewSearch { get; set; }
public bool EnableIssues { get; set; }
} }
} }

@ -0,0 +1,37 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CouchPotatoSettings.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace Ombi.Core.SettingModels
{
public sealed class EmbySettings : ExternalSettings
{
public bool Enable { get; set; }
public string ApiKey { get; set; }
public string AdministratorId { get; set; }
public bool EnableEpisodeSearching { get; set; }
}
}

@ -0,0 +1,35 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: EmailNotificationSettings.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.SettingModels
{
public sealed class MassEmailSettings : NotificationSettings
{
public string Users { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
}

@ -33,6 +33,8 @@ namespace Ombi.Core.SettingModels
{ {
AdvancedSearch = true; AdvancedSearch = true;
} }
public bool Enable { get; set; }
public bool AdvancedSearch { get; set; } public bool AdvancedSearch { get; set; }
public bool EnableTvEpisodeSearching { get; set; } public bool EnableTvEpisodeSearching { get; set; }

@ -32,6 +32,6 @@ namespace Ombi.Core.SettingModels
public string ApiKey { get; set; } public string ApiKey { get; set; }
public string QualityProfile { get; set; } public string QualityProfile { get; set; }
public string RootPath { get; set; } public string RootPath { get; set; }
public string FullRootPath { get; set; }
} }
} }

@ -47,5 +47,10 @@ namespace Ombi.Core.SettingModels
public int PlexContentCacher { get; set; } public int PlexContentCacher { get; set; }
public int PlexUserChecker { get; set; } public int PlexUserChecker { get; set; }
public int RadarrCacher { get; set; } public int RadarrCacher { get; set; }
public int EmbyEpisodeCacher { get; set; }
public int EmbyContentCacher { get; set; }
public int EmbyAvailabilityChecker { get; set; }
public int EmbyUserChecker { get; set; }
} }
} }

@ -37,7 +37,7 @@ namespace Ombi.Core.SettingModels
public Dictionary<string, string> Qualities => new Dictionary<string, string> public Dictionary<string, string> Qualities => new Dictionary<string, string>
{ {
{ "default", "Use Deafult" }, { "default", "Use Default" },
{ "sdtv", "SD TV" }, { "sdtv", "SD TV" },
{ "sddvd", "SD DVD" }, { "sddvd", "SD DVD" },
{ "hdtv", "HD TV" }, { "hdtv", "HD TV" },

@ -46,7 +46,7 @@ namespace Ombi.Core
{ {
Db = new DbConfiguration(new SqliteFactory()); Db = new DbConfiguration(new SqliteFactory());
var created = Db.CheckDb(); var created = Db.CheckDb();
TableCreation.CreateTables(Db.DbConnection()); Db.DbConnection().CreateTables();
if (created) if (created)
{ {
@ -55,7 +55,7 @@ namespace Ombi.Core
else else
{ {
// Shrink DB // Shrink DB
TableCreation.Vacuum(Db.DbConnection()); Db.DbConnection().Vacuum();
} }
// Add the new 'running' item into the scheduled jobs so we can check if the cachers are running // Add the new 'running' item into the scheduled jobs so we can check if the cachers are running
@ -114,6 +114,7 @@ namespace Ombi.Core
try try
{ {
Task.Run(() => { CacheSonarrQualityProfiles(mc); }); Task.Run(() => { CacheSonarrQualityProfiles(mc); });
Task.Run(() => { CacheRadarrQualityProfiles(mc); });
Task.Run(() => { CacheCouchPotatoQualityProfiles(mc); }); Task.Run(() => { CacheCouchPotatoQualityProfiles(mc); });
// we don't need to cache sickrage profiles, those are static // we don't need to cache sickrage profiles, those are static
} }
@ -127,7 +128,6 @@ namespace Ombi.Core
{ {
try try
{ {
Log.Info("Executing GetSettings call to Sonarr for quality profiles");
var sonarrSettingsService = new SettingsServiceV2<SonarrSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider)); var sonarrSettingsService = new SettingsServiceV2<SonarrSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider));
var sonarrSettings = sonarrSettingsService.GetSettings(); var sonarrSettings = sonarrSettingsService.GetSettings();
if (sonarrSettings.Enabled) if (sonarrSettings.Enabled)
@ -145,11 +145,31 @@ namespace Ombi.Core
} }
} }
private void CacheRadarrQualityProfiles(ICacheProvider cacheProvider)
{
try
{
var radarrService = new SettingsServiceV2<RadarrSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider));
var radarrSettings = radarrService.GetSettings();
if (radarrSettings.Enabled)
{
Log.Info("Begin executing GetProfiles call to Radarr for quality profiles");
RadarrApi radarrApi = new RadarrApi();
var profiles = radarrApi.GetProfiles(radarrSettings.ApiKey, radarrSettings.FullUri);
cacheProvider.Set(CacheKeys.RadarrQualityProfiles, profiles);
Log.Info("Finished executing GetProfiles call to Radarr for quality profiles");
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to cache Sonarr quality profiles!");
}
}
private void CacheCouchPotatoQualityProfiles(ICacheProvider cacheProvider) private void CacheCouchPotatoQualityProfiles(ICacheProvider cacheProvider)
{ {
try try
{ {
Log.Info("Executing GetSettings call to CouchPotato for quality profiles");
var cpSettingsService = new SettingsServiceV2<CouchPotatoSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider)); var cpSettingsService = new SettingsServiceV2<CouchPotatoSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider));
var cpSettings = cpSettingsService.GetSettings(); var cpSettings = cpSettingsService.GetSettings();
if (cpSettings.Enabled) if (cpSettings.Enabled)

@ -30,22 +30,26 @@ using System.Linq;
using Ombi.Core.Models; using Ombi.Core.Models;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Helpers.Permissions; using Ombi.Helpers.Permissions;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository; using Ombi.Store.Repository;
namespace Ombi.Core.Users namespace Ombi.Core.Users
{ {
public class UserHelper : IUserHelper public class UserHelper : IUserHelper
{ {
public UserHelper(IUserRepository userRepository, IPlexUserRepository plexUsers, ISecurityExtensions security) public UserHelper(IUserRepository userRepository, IExternalUserRepository<PlexUsers> plexUsers, IExternalUserRepository<EmbyUsers> emby, ISecurityExtensions security)
{ {
LocalUserRepository = userRepository; LocalUserRepository = userRepository;
PlexUserRepository = plexUsers; PlexUserRepository = plexUsers;
Security = security; Security = security;
EmbyUserRepository = emby;
} }
private IUserRepository LocalUserRepository { get; } private IUserRepository LocalUserRepository { get; }
private IPlexUserRepository PlexUserRepository { get; } private IExternalUserRepository<PlexUsers> PlexUserRepository { get; }
private ISecurityExtensions Security { get; } private ISecurityExtensions Security { get; }
private IExternalUserRepository<EmbyUsers> EmbyUserRepository { get; }
public IEnumerable<UserHelperModel> GetUsers() public IEnumerable<UserHelperModel> GetUsers()
@ -53,7 +57,8 @@ namespace Ombi.Core.Users
var model = new List<UserHelperModel>(); var model = new List<UserHelperModel>();
var localUsers = LocalUserRepository.GetAll(); var localUsers = LocalUserRepository.GetAll();
var plexUsers = PlexUserRepository.GetAll(); var plexUsers = PlexUserRepository.GetAll().ToList();
var embyUsers = EmbyUserRepository.GetAll().ToList();
foreach (var user in localUsers) foreach (var user in localUsers)
{ {
@ -68,14 +73,30 @@ namespace Ombi.Core.Users
}); });
} }
model.AddRange(plexUsers.Select(user => new UserHelperModel if (plexUsers.Any())
{ {
Type = UserType.LocalUser, model.AddRange(plexUsers.Select(user => new UserHelperModel
Username = user.Username, {
UserAlias = user.UserAlias, Type = UserType.PlexUser,
EmailAddress = user.EmailAddress, Username = user.Username,
Permissions = (Permissions)user.Permissions UserAlias = user.UserAlias,
})); EmailAddress = user.EmailAddress,
Permissions = (Permissions) user.Permissions
}));
}
if (embyUsers.Any())
{
model.AddRange(embyUsers.Select(user => new UserHelperModel
{
Type = UserType.EmbyUser,
Username = user.Username,
UserAlias = user.UserAlias,
EmailAddress = user.EmailAddress,
Permissions = (Permissions)user.Permissions
}));
}
return model; return model;
} }
@ -86,9 +107,11 @@ namespace Ombi.Core.Users
var localUsers = LocalUserRepository.GetAll().ToList(); var localUsers = LocalUserRepository.GetAll().ToList();
var plexUsers = PlexUserRepository.GetAll().ToList(); var plexUsers = PlexUserRepository.GetAll().ToList();
var embyUsers = EmbyUserRepository.GetAll().ToList();
var filteredLocal = localUsers.Where(x => ((Permissions)x.Permissions).HasFlag(permission)); var filteredLocal = localUsers.Where(x => ((Permissions)x.Permissions).HasFlag(permission));
var filteredPlex = plexUsers.Where(x => ((Permissions)x.Permissions).HasFlag(permission)); var filteredPlex = plexUsers.Where(x => ((Permissions)x.Permissions).HasFlag(permission));
var filteredEmby = embyUsers.Where(x => ((Permissions)x.Permissions).HasFlag(permission));
foreach (var user in filteredLocal) foreach (var user in filteredLocal)
@ -107,7 +130,17 @@ namespace Ombi.Core.Users
model.AddRange(filteredPlex.Select(user => new UserHelperModel model.AddRange(filteredPlex.Select(user => new UserHelperModel
{ {
Type = UserType.LocalUser, Type = UserType.PlexUser,
Username = user.Username,
UserAlias = user.UserAlias,
EmailAddress = user.EmailAddress,
Permissions = (Permissions)user.Permissions,
Features = (Features)user.Features
}));
model.AddRange(filteredEmby.Select(user => new UserHelperModel
{
Type = UserType.EmbyUser,
Username = user.Username, Username = user.Username,
UserAlias = user.UserAlias, UserAlias = user.UserAlias,
EmailAddress = user.EmailAddress, EmailAddress = user.EmailAddress,
@ -115,6 +148,7 @@ namespace Ombi.Core.Users
Features = (Features)user.Features Features = (Features)user.Features
})); }));
return model; return model;
} }
@ -124,9 +158,11 @@ namespace Ombi.Core.Users
var localUsers = LocalUserRepository.GetAll().ToList(); var localUsers = LocalUserRepository.GetAll().ToList();
var plexUsers = PlexUserRepository.GetAll().ToList(); var plexUsers = PlexUserRepository.GetAll().ToList();
var embyUsers = PlexUserRepository.GetAll().ToList();
var filteredLocal = localUsers.Where(x => ((Features)x.Features).HasFlag(features)); var filteredLocal = localUsers.Where(x => ((Features)x.Features).HasFlag(features));
var filteredPlex = plexUsers.Where(x => ((Features)x.Features).HasFlag(features)); var filteredPlex = plexUsers.Where(x => ((Features)x.Features).HasFlag(features));
var filteredEmby = embyUsers.Where(x => ((Features)x.Features).HasFlag(features));
foreach (var user in filteredLocal) foreach (var user in filteredLocal)
@ -145,7 +181,17 @@ namespace Ombi.Core.Users
model.AddRange(filteredPlex.Select(user => new UserHelperModel model.AddRange(filteredPlex.Select(user => new UserHelperModel
{ {
Type = UserType.LocalUser, Type = UserType.PlexUser,
Username = user.Username,
UserAlias = user.UserAlias,
EmailAddress = user.EmailAddress,
Permissions = (Permissions)user.Permissions,
Features = (Features)user.Features
}));
model.AddRange(filteredEmby.Select(user => new UserHelperModel
{
Type = UserType.EmbyUser,
Username = user.Username, Username = user.Username,
UserAlias = user.UserAlias, UserAlias = user.UserAlias,
EmailAddress = user.EmailAddress, EmailAddress = user.EmailAddress,

@ -48,7 +48,7 @@ namespace Ombi.Helpers.Tests
var consts = typeof(UserClaims).GetConstantsValues<string>(); var consts = typeof(UserClaims).GetConstantsValues<string>();
Assert.That(consts.Contains("Admin"),Is.True); Assert.That(consts.Contains("Admin"),Is.True);
Assert.That(consts.Contains("PowerUser"),Is.True); Assert.That(consts.Contains("PowerUser"),Is.True);
Assert.That(consts.Contains("User"),Is.True); Assert.That(consts.Contains("RegularUser"),Is.True);
} }
private static IEnumerable<TestCaseData> TypeData private static IEnumerable<TestCaseData> TypeData
@ -59,14 +59,7 @@ namespace Ombi.Helpers.Tests
yield return new TestCaseData(typeof(int)).Returns(new string[0]).SetName("NoPropeties Class"); yield return new TestCaseData(typeof(int)).Returns(new string[0]).SetName("NoPropeties Class");
yield return new TestCaseData(typeof(IEnumerable<>)).Returns(new string[0]).SetName("Interface"); yield return new TestCaseData(typeof(IEnumerable<>)).Returns(new string[0]).SetName("Interface");
yield return new TestCaseData(typeof(string)).Returns(new[] { "Chars", "Length" }).SetName("String"); yield return new TestCaseData(typeof(string)).Returns(new[] { "Chars", "Length" }).SetName("String");
yield return new TestCaseData(typeof(RequestedModel)).Returns(
new[]
{
"ProviderId", "ImdbId", "TvDbId", "Overview", "Title", "PosterPath", "ReleaseDate", "Type",
"Status", "Approved", "RequestedBy", "RequestedDate", "Available", "Issues", "OtherMessage", "AdminNote",
"SeasonList", "SeasonCount", "SeasonsRequested", "MusicBrainzId", "RequestedUsers","ArtistName",
"ArtistId","IssueId","Episodes", "Denied", "DeniedReason", "AllUsers","CanApprove","Id",
}).SetName("Requested Model");
} }
} }

@ -25,6 +25,7 @@
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@ -49,5 +50,10 @@ namespace Ombi.Helpers
return sb.ToString(); return sb.ToString();
} }
} }
public static string GetSha1Hash(string input)
{
return string.Join("", (new SHA1Managed().ComputeHash(Encoding.UTF8.GetBytes(input))).Select(x => x.ToString("x2")).ToArray());
}
} }
} }

@ -30,6 +30,7 @@ namespace Ombi.Helpers
public enum UserType public enum UserType
{ {
PlexUser, PlexUser,
LocalUser LocalUser,
EmbyUser
} }
} }

@ -249,7 +249,7 @@
// }); // });
// CacheMock.Setup(x => x.Get<List<PlexSearch>>(CacheKeys.PlexLibaries)).Returns(cachedMovies); // CacheMock.Setup(x => x.Get<List<PlexSearch>>(CacheKeys.PlexLibaries)).Returns(cachedMovies);
// SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create<PlexSettings>()); // SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create<PlexSettings>());
// var movies = Checker.GetPlexMovies(); // var movies = Checker.GetEmbyMovies();
// Assert.That(movies.Any(x => x.ProviderId == "1212")); // Assert.That(movies.Any(x => x.ProviderId == "1212"));
// } // }
@ -267,7 +267,7 @@
// }); // });
// SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create<PlexSettings>()); // SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create<PlexSettings>());
// CacheMock.Setup(x => x.Get<List<PlexSearch>>(CacheKeys.PlexLibaries)).Returns(cachedTv); // CacheMock.Setup(x => x.Get<List<PlexSearch>>(CacheKeys.PlexLibaries)).Returns(cachedTv);
// var movies = Checker.GetPlexTvShows(); // var movies = Checker.GetEmbyTvShows();
// Assert.That(movies.Any(x => x.ProviderId == "1212")); // Assert.That(movies.Any(x => x.ProviderId == "1212"));
// } // }

@ -0,0 +1,6 @@
namespace Ombi.Services.Interfaces
{
public interface IEmbyNotificationEngine : INotificationEngine
{
}
}

@ -0,0 +1,12 @@
using Quartz;
namespace Ombi.Services.Jobs
{
public interface IMassEmail
{
void Execute(IJobExecutionContext context);
void MassEmailAdminTest(string html, string subject);
void SendMassEmail(string html, string subject);
}
}

@ -34,7 +34,7 @@ namespace Ombi.Services.Interfaces
{ {
public interface INotificationEngine public interface INotificationEngine
{ {
Task NotifyUsers(IEnumerable<RequestedModel> modelChanged, string apiKey, NotificationType type); Task NotifyUsers(IEnumerable<RequestedModel> modelChanged, NotificationType type);
Task NotifyUsers(RequestedModel modelChanged, string apiKey, NotificationType type); Task NotifyUsers(RequestedModel modelChanged, NotificationType type);
} }
} }

@ -0,0 +1,6 @@
namespace Ombi.Services.Interfaces
{
public interface IPlexNotificationEngine : INotificationEngine
{
}
}

@ -5,7 +5,7 @@ namespace Ombi.Services.Jobs
public interface IRecentlyAdded public interface IRecentlyAdded
{ {
void Execute(IJobExecutionContext context); void Execute(IJobExecutionContext context);
void Test(); void RecentlyAddedAdminTest();
void Start(); void StartNewsLetter();
} }
} }

@ -0,0 +1,362 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Models;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Quartz;
using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType;
namespace Ombi.Services.Jobs
{
public class EmbyAvailabilityChecker : IJob, IEmbyAvailabilityChecker
{
public EmbyAvailabilityChecker(ISettingsService<EmbySettings> embySettings, IRequestService request, IEmbyApi emby, ICacheProvider cache,
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<EmbyEpisodes> repo, IEmbyNotificationEngine e, IRepository<EmbyContent> content)
{
Emby = embySettings;
RequestService = request;
EmbyApi = emby;
Cache = cache;
Notification = notify;
Job = rec;
UserNotifyRepo = users;
EpisodeRepo = repo;
NotificationEngine = e;
EmbyContent = content;
}
private ISettingsService<EmbySettings> Emby { get; }
private IRepository<EmbyEpisodes> EpisodeRepo { get; }
private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IEmbyApi EmbyApi { get; }
private ICacheProvider Cache { get; }
private INotificationService Notification { get; }
private IJobRecord Job { get; }
private IRepository<UsersToNotify> UserNotifyRepo { get; }
private INotificationEngine NotificationEngine { get; }
private IRepository<EmbyContent> EmbyContent { get; }
public void CheckAndUpdateAll()
{
var embySettings = Emby.GetSettings();
if (!embySettings.Enable)
{
return;
}
if (!ValidateSettings(embySettings))
{
Log.Debug("Validation of the Emby settings failed.");
return;
}
var content = EmbyContent.GetAll().ToList();
var movies = GetEmbyMovies(content).ToArray();
var shows = GetEmbyTvShows(content).ToArray();
var albums = GetEmbyMusic(content).ToArray();
var requests = RequestService.GetAll();
var requestedModels = requests as RequestedModel[] ?? requests.Where(x => !x.Available).ToArray();
if (!requestedModels.Any())
{
Log.Debug("There are no requests to check.");
return;
}
var modifiedModel = new List<RequestedModel>();
foreach (var r in requestedModels)
{
var releaseDate = r.ReleaseDate == DateTime.MinValue ? string.Empty : r.ReleaseDate.ToString("yyyy");
bool matchResult;
switch (r.Type)
{
case RequestType.Movie:
matchResult = IsMovieAvailable(movies, r.Title, releaseDate, r.ImdbId);
break;
case RequestType.TvShow:
if (!embySettings.EnableEpisodeSearching)
{
matchResult = IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList);
}
else
{
matchResult = r.Episodes.Any() ?
r.Episodes.All(x => IsEpisodeAvailable(r.TvDbId, x.SeasonNumber, x.EpisodeNumber)) :
IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList);
}
break;
case RequestType.Album:
//matchResult = IsAlbumAvailable(albums, r.Title, r.ReleaseDate.Year.ToString(), r.ArtistName); // TODO Emby
matchResult = false;
break;
default:
throw new ArgumentOutOfRangeException();
}
if (matchResult)
{
r.Available = true;
modifiedModel.Add(r);
continue;
}
}
Log.Debug("Requests that will be updated count {0}", modifiedModel.Count);
if (modifiedModel.Any())
{
NotificationEngine.NotifyUsers(modifiedModel, NotificationType.RequestAvailable);
RequestService.BatchUpdate(modifiedModel);
}
}
public IEnumerable<EmbyContent> GetEmbyMovies(IEnumerable<EmbyContent> content)
{
return content.Where(x => x.Type == EmbyMediaType.Movie);
}
public bool IsMovieAvailable(EmbyContent[] embyMovies, string title, string year, string providerId)
{
var movie = GetMovie(embyMovies, title, year, providerId);
return movie != null;
}
public EmbyContent GetMovie(EmbyContent[] embyMovies, string title, string year, string providerId)
{
if (embyMovies.Length == 0)
{
return null;
}
foreach (var movie in embyMovies)
{
if (string.IsNullOrEmpty(movie.Title) || movie.PremierDate == DateTime.MinValue)
{
continue;
}
if (!string.IsNullOrEmpty(movie.ProviderId) &&
movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
return movie;
}
if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) &&
movie.PremierDate.Year.ToString().Equals(year, StringComparison.CurrentCultureIgnoreCase))
{
return movie;
}
}
return null;
}
public IEnumerable<EmbyContent> GetEmbyTvShows(IEnumerable<EmbyContent> content)
{
return content.Where(x => x.Type == EmbyMediaType.Series);
}
public bool IsTvShowAvailable(EmbyContent[] embyShows, string title, string year, string providerId, int[] seasons = null)
{
var show = GetTvShow(embyShows, title, year, providerId, seasons);
return show != null;
}
public EmbyContent GetTvShow(EmbyContent[] embyShows, string title, string year, string providerId,
int[] seasons = null)
{
foreach (var show in embyShows)
{
//if (show.ProviderId == providerId && seasons != null) // TODO Emby
//{
// var showSeasons = ByteConverterHelper.ReturnObject<int[]>(show.Seasons);
// if (seasons.Any(season => showSeasons.Contains(season)))
// {
// return show;
// }
// return null;
//}
if (!string.IsNullOrEmpty(show.ProviderId) &&
show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
return show;
}
if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) &&
show.PremierDate.Year.ToString().Equals(year, StringComparison.CurrentCultureIgnoreCase))
{
return show;
}
}
return null;
}
public bool IsEpisodeAvailable(string theTvDbId, int season, int episode)
{
var ep = EpisodeRepo.Custom(
connection =>
{
connection.Open();
var result = connection.Query<EmbyEpisodes>("select * from EmbyEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId });
return result;
}).ToList();
if (!ep.Any())
{
Log.Info("Episode cache info is not available. tvdbid: {0}, season: {1}, episode: {2}", theTvDbId, season, episode);
return false;
}
foreach (var result in ep)
{
if (result.ProviderId.Equals(theTvDbId) && result.EpisodeNumber == episode && result.SeasonNumber == season)
{
return true;
}
}
return false;
}
/// <summary>
/// Gets the episode's db in the cache.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<EmbyEpisodes>> GetEpisodes()
{
var episodes = await EpisodeRepo.GetAllAsync();
if (episodes == null)
{
return new HashSet<EmbyEpisodes>();
}
return episodes;
}
/// <summary>
/// Gets the episode's stored in the db and then filters on the TheTvDBId.
/// </summary>
/// <param name="theTvDbId">The tv database identifier.</param>
/// <returns></returns>
public async Task<IEnumerable<EmbyEpisodes>> GetEpisodes(int theTvDbId)
{
var ep = await EpisodeRepo.CustomAsync(async connection =>
{
connection.Open();
var result = await connection.QueryAsync<EmbyEpisodes>(@"select ee.* from EmbyEpisodes ee inner join EmbyContent ec
on ee.ParentId = ec.EmbyId
where ec.ProviderId = @ProviderId", new { ProviderId = theTvDbId });
return result;
});
var embyEpisodes = ep as EmbyEpisodes[] ?? ep.ToArray();
if (!embyEpisodes.Any())
{
Log.Info("Episode db info is not available.");
return new List<EmbyEpisodes>();
}
return embyEpisodes;
}
public IEnumerable<EmbyContent> GetEmbyMusic(IEnumerable<EmbyContent> content)
{
return content.Where(x => x.Type == EmbyMediaType.Music);
}
private bool ValidateSettings(EmbySettings emby)
{
if (emby.Enable)
{
if (string.IsNullOrEmpty(emby?.Ip) || string.IsNullOrEmpty(emby?.ApiKey) || string.IsNullOrEmpty(emby?.AdministratorId))
{
Log.Warn("A setting is null, Ensure Emby is configured correctly");
return false;
}
}
return emby.Enable;
}
public void Execute(IJobExecutionContext context)
{
Job.SetRunning(true, JobNames.EmbyChecker);
try
{
CheckAndUpdateAll();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyChecker);
Job.SetRunning(false, JobNames.EmbyChecker);
}
}
public void Start()
{
Job.SetRunning(true, JobNames.EmbyChecker);
try
{
CheckAndUpdateAll();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyChecker);
Job.SetRunning(false, JobNames.EmbyChecker);
}
}
}
}

@ -0,0 +1,273 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.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 Dapper;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs.Interfaces;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
using Quartz;
using EmbyMediaType = Ombi.Api.Models.Emby.EmbyMediaType;
namespace Ombi.Services.Jobs
{
public class EmbyContentCacher : IJob, IEmbyContentCacher
{
public EmbyContentCacher(ISettingsService<EmbySettings> embySettings, IRequestService request, IEmbyApi emby, ICacheProvider cache,
IJobRecord rec, IRepository<EmbyEpisodes> repo, IRepository<EmbyContent> content)
{
Emby = embySettings;
RequestService = request;
EmbyApi = emby;
Cache = cache;
Job = rec;
EpisodeRepo = repo;
EmbyContent = content;
}
private ISettingsService<EmbySettings> Emby { get; }
private IRepository<EmbyEpisodes> EpisodeRepo { get; }
private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IEmbyApi EmbyApi { get; }
private ICacheProvider Cache { get; }
private IJobRecord Job { get; }
private IRepository<EmbyContent> EmbyContent { get; }
public void CacheContent()
{
var embySettings = Emby.GetSettings();
if (!embySettings.Enable)
{
return;
}
if (!ValidateSettings(embySettings))
{
Log.Debug("Validation of emby settings failed.");
return;
}
CachedLibraries(embySettings);
}
public List<EmbyMovieItem> GetMovies()
{
var settings = Emby.GetSettings();
return EmbyApi.GetAllMovies(settings.ApiKey, settings.AdministratorId, settings.FullUri).Items;
}
public List<EmbySeriesItem> GetTvShows()
{
var settings = Emby.GetSettings();
return EmbyApi.GetAllShows(settings.ApiKey, settings.AdministratorId, settings.FullUri).Items;
}
private void CachedLibraries(EmbySettings embySettings)
{
if (!ValidateSettings(embySettings))
{
Log.Warn("The settings are not configured");
}
try
{
var movies = GetMovies();
foreach (var m in movies)
{
if (m.Type.Equals("boxset", StringComparison.CurrentCultureIgnoreCase))
{
var info = EmbyApi.GetCollection(m.Id, embySettings.ApiKey,
embySettings.AdministratorId, embySettings.FullUri);
foreach (var item in info.Items)
{
var movieInfo = EmbyApi.GetInformation(item.Id, EmbyMediaType.Movie, embySettings.ApiKey,
embySettings.AdministratorId, embySettings.FullUri).MovieInformation;
ProcessMovies(movieInfo);
}
}
else
{
var movieInfo = EmbyApi.GetInformation(m.Id, EmbyMediaType.Movie, embySettings.ApiKey,
embySettings.AdministratorId, embySettings.FullUri).MovieInformation;
ProcessMovies(movieInfo);
}
}
var tv = GetTvShows();
foreach (var t in tv)
{
var tvInfo = EmbyApi.GetInformation(t.Id, EmbyMediaType.Series, embySettings.ApiKey,
embySettings.AdministratorId, embySettings.FullUri).SeriesInformation;
if (string.IsNullOrEmpty(tvInfo.ProviderIds?.Tvdb))
{
Log.Error("Provider Id on tv {0} is null", t.Name);
continue;
}
// Check if it exists
var item = EmbyContent.Custom(connection =>
{
connection.Open();
var media = connection.QueryFirstOrDefault<EmbyContent>("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { ProviderId = tvInfo.ProviderIds.Tvdb, type = 1 });
connection.Dispose();
return media;
});
if (item == null)
{
EmbyContent.Insert(new EmbyContent
{
ProviderId = tvInfo.ProviderIds.Tvdb,
PremierDate = tvInfo.PremiereDate,
Title = tvInfo.Name,
Type = Store.Models.Plex.EmbyMediaType.Series,
EmbyId = t.Id,
AddedAt = DateTime.UtcNow
});
}
}
//TODO Emby
//var albums = GetPlexAlbums(results);
//foreach (var a in albums)
//{
// if (string.IsNullOrEmpty(a.ProviderId))
// {
// Log.Error("Provider Id on album {0} is null", a.Title);
// continue;
// }
// // Check if it exists
// var item = EmbyContent.Custom(connection =>
// {
// connection.Open();
// var media = connection.QueryFirstOrDefault<PlexContent>("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { a.ProviderId, type = 2 });
// connection.Dispose();
// return media;
// });
// if (item == null)
// {
// EmbyContent.Insert(new PlexContent
// {
// ProviderId = a.ProviderId,
// ReleaseYear = a.ReleaseYear ?? string.Empty,
// Title = a.Title,
// Type = Store.Models.Plex.PlexMediaType.Artist,
// Url = a.Url
// });
// }
//}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to obtain Emby libraries");
}
}
private bool ValidateSettings(EmbySettings emby)
{
if (emby.Enable)
{
if (emby?.Ip == null || string.IsNullOrEmpty(emby?.ApiKey))
{
Log.Warn("A setting is null, Ensure Emby is configured correctly, and we have a Emby Auth token.");
return false;
}
}
return emby.Enable;
}
public void Execute(IJobExecutionContext context)
{
Job.SetRunning(true, JobNames.EmbyCacher);
try
{
CacheContent();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyCacher);
Job.SetRunning(false, JobNames.EmbyCacher);
}
}
private void ProcessMovies(EmbyMovieInformation movieInfo)
{
if (string.IsNullOrEmpty(movieInfo.ProviderIds.Imdb))
{
Log.Error("Provider Id on movie {0} is null", movieInfo.Name);
return;
}
// Check if it exists
var item = EmbyContent.Custom(connection =>
{
connection.Open();
var media = connection.QueryFirstOrDefault<EmbyContent>("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { ProviderId = movieInfo.ProviderIds.Imdb, type = 0 });
connection.Dispose();
return media;
});
if (item == null)
{
// Doesn't exist, insert it
EmbyContent.Insert(new EmbyContent
{
ProviderId = movieInfo.ProviderIds.Imdb,
PremierDate = movieInfo.PremiereDate,
Title = movieInfo.Name,
Type = Store.Models.Plex.EmbyMediaType.Movie,
EmbyId = movieInfo.Id,
AddedAt = DateTime.UtcNow
});
}
}
}
}

@ -0,0 +1,170 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexEpisodeCacher.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using Dapper;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs.Interfaces;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Services.Jobs
{
public class EmbyEpisodeCacher : IJob, IEmbyEpisodeCacher
{
public EmbyEpisodeCacher(ISettingsService<EmbySettings> embySettings, IEmbyApi emby, ICacheProvider cache,
IJobRecord rec, IRepository<EmbyEpisodes> repo, ISettingsService<ScheduledJobsSettings> jobs)
{
Emby = embySettings;
EmbyApi = emby;
Cache = cache;
Job = rec;
Repo = repo;
Jobs = jobs;
}
private ISettingsService<EmbySettings> Emby { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IEmbyApi EmbyApi { get; }
private ICacheProvider Cache { get; }
private IJobRecord Job { get; }
private IRepository<EmbyEpisodes> Repo { get; }
private ISettingsService<ScheduledJobsSettings> Jobs { get; }
private const string TableName = "EmbyEpisodes";
// Note, once an episode exists, we store it and it always exists.
// We might want to look at checking if something has been removed from the server in the future.
public void CacheEpisodes(EmbySettings settings)
{
var allEpisodes = EmbyApi.GetAllEpisodes(settings.ApiKey, settings.AdministratorId, settings.FullUri);
var model = new List<EmbyEpisodes>();
foreach (var ep in allEpisodes.Items)
{
var epInfo = EmbyApi.GetInformation(ep.Id, EmbyMediaType.Episode, settings.ApiKey,
settings.AdministratorId, settings.FullUri);
if (epInfo.EpisodeInformation?.ProviderIds?.Tvdb == null)
{
continue;
}
// Check it this episode exists
var item = Repo.Custom(connection =>
{
connection.Open();
var media =
connection.QueryFirstOrDefault<EmbyEpisodes>(
"select * from EmbyEpisodes where ProviderId = @ProviderId",
new {ProviderId = epInfo.EpisodeInformation?.ProviderIds?.Tvdb});
connection.Dispose();
return media;
});
if (item == null)
{
// add it
model.Add(new EmbyEpisodes
{
EmbyId = ep.Id,
EpisodeNumber = ep.IndexNumber,
SeasonNumber = ep.ParentIndexNumber,
EpisodeTitle = ep.Name,
ParentId = ep.SeriesId,
ShowTitle = ep.SeriesName,
ProviderId = epInfo.EpisodeInformation.ProviderIds.Tvdb,
AddedAt = DateTime.UtcNow
});
}
}
// Insert the new items
var result = Repo.BatchInsert(model, TableName, typeof(EmbyEpisodes).GetPropertyNames());
if (!result)
{
Log.Error("Saving the emby episodes to the DB Failed");
}
}
public void Start()
{
try
{
var s = Emby.GetSettings();
if (!s.EnableEpisodeSearching)
{
return;
}
Job.SetRunning(true, JobNames.EmbyEpisodeCacher);
CacheEpisodes(s);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyEpisodeCacher);
Job.SetRunning(false, JobNames.EmbyEpisodeCacher);
}
}
public void Execute(IJobExecutionContext context)
{
try
{
var s = Emby.GetSettings();
if (!s.EnableEpisodeSearching)
{
return;
}
Job.SetRunning(true, JobNames.EmbyEpisodeCacher);
CacheEpisodes(s);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyEpisodeCacher);
Job.SetRunning(false, JobNames.EmbyEpisodeCacher);
}
}
}
}

@ -0,0 +1,136 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StoreCleanup.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.Linq;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.Users;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Services.Jobs
{
public class EmbyUserChecker : IJob, IEmbyUserChecker
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public EmbyUserChecker(IExternalUserRepository<EmbyUsers> plexUsers, IEmbyApi embyApi, IJobRecord rec, ISettingsService<EmbySettings> embyS, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<UserManagementSettings> umSettings,
IRequestService requestService, IUserRepository localUser)
{
Repo = plexUsers;
JobRecord = rec;
EmbyApi = embyApi;
EmbySettings = embyS;
PlexRequestSettings = prSettings;
UserManagementSettings = umSettings;
RequestService = requestService;
LocalUserRepository = localUser;
}
private IJobRecord JobRecord { get; }
private IEmbyApi EmbyApi { get; }
private IExternalUserRepository<EmbyUsers> Repo { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
private IRequestService RequestService { get; }
private IUserRepository LocalUserRepository { get; }
public void Start()
{
JobRecord.SetRunning(true, JobNames.EmbyUserChecker);
try
{
var settings = EmbySettings.GetSettings();
if (string.IsNullOrEmpty(settings.ApiKey) || !settings.Enable)
{
return;
}
var embyUsers = EmbyApi.GetUsers(settings.FullUri, settings.ApiKey);
var userManagementSettings = UserManagementSettings.GetSettings();
var dbUsers = Repo.GetAll().ToList();
// Regular users
foreach (var user in embyUsers)
{
var dbUser = dbUsers.FirstOrDefault(x => x.EmbyUserId == user.Id);
if (dbUser != null)
{
// we already have a user
continue;
}
// Looks like it's a new user!
var m = new EmbyUsers
{
EmbyUserId = user.Id,
Permissions = UserManagementHelper.GetPermissions(userManagementSettings),
Features = UserManagementHelper.GetFeatures(userManagementSettings),
UserAlias = string.Empty,
Username = user.Name,
LoginId = Guid.NewGuid().ToString()
};
// If it's the admin, give them the admin permission
if (user.Policy.IsAdministrator)
{
if (!((Permissions) m.Permissions).HasFlag(Permissions.Administrator))
{
m.Permissions += (int)Permissions.Administrator;
}
}
Repo.Insert(m);
}
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
JobRecord.SetRunning(false, JobNames.EmbyUserChecker);
JobRecord.Record(JobNames.EmbyUserChecker);
}
}
public void Execute(IJobExecutionContext context)
{
Start();
}
}
}

@ -0,0 +1,10 @@
using Quartz;
namespace Ombi.Services.Jobs
{
public interface IEmbyUserChecker
{
void Execute(IJobExecutionContext context);
void Start();
}
}

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Store.Models.Emby;
using Quartz;
namespace Ombi.Services.Jobs
{
public interface IEmbyAvailabilityChecker
{
void CheckAndUpdateAll();
void Execute(IJobExecutionContext context);
IEnumerable<EmbyContent> GetEmbyMovies(IEnumerable<EmbyContent> content);
IEnumerable<EmbyContent> GetEmbyMusic(IEnumerable<EmbyContent> content);
IEnumerable<EmbyContent> GetEmbyTvShows(IEnumerable<EmbyContent> content);
Task<IEnumerable<EmbyEpisodes>> GetEpisodes();
Task<IEnumerable<EmbyEpisodes>> GetEpisodes(int theTvDbId);
EmbyContent GetMovie(EmbyContent[] embyMovies, string title, string year, string providerId);
EmbyContent GetTvShow(EmbyContent[] embyShows, string title, string year, string providerId, int[] seasons = null);
bool IsEpisodeAvailable(string theTvDbId, int season, int episode);
bool IsMovieAvailable(EmbyContent[] embyMovies, string title, string year, string providerId);
bool IsTvShowAvailable(EmbyContent[] embyShows, string title, string year, string providerId, int[] seasons = null);
void Start();
}
}

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Ombi.Api.Models.Emby;
using Quartz;
namespace Ombi.Services.Jobs.Interfaces
{
public interface IEmbyContentCacher
{
void CacheContent();
void Execute(IJobExecutionContext context);
List<EmbyMovieItem> GetMovies();
List<EmbySeriesItem> GetTvShows();
}
}

@ -0,0 +1,12 @@
using Ombi.Core.SettingModels;
using Quartz;
namespace Ombi.Services.Jobs.Interfaces
{
public interface IEmbyEpisodeCacher
{
void CacheEpisodes(EmbySettings settings);
void Execute(IJobExecutionContext context);
void Start();
}
}

@ -35,13 +35,17 @@ namespace Ombi.Services.Jobs
public const string RadarrCacher = "Radarr Cacher"; public const string RadarrCacher = "Radarr Cacher";
public const string SrCacher = "SickRage Cacher"; public const string SrCacher = "SickRage Cacher";
public const string PlexChecker = "Plex Availability Cacher"; public const string PlexChecker = "Plex Availability Cacher";
public const string EmbyChecker = "Emby Availability Cacher";
public const string PlexCacher = "Plex Cacher"; public const string PlexCacher = "Plex Cacher";
public const string EmbyCacher = "Emby Cacher";
public const string StoreCleanup = "Database Cleanup"; public const string StoreCleanup = "Database Cleanup";
public const string RequestLimitReset = "Request Limit Reset"; public const string RequestLimitReset = "Request Limit Reset";
public const string EpisodeCacher = "Plex Episode Cacher"; public const string EpisodeCacher = "Plex Episode Cacher";
public const string EmbyEpisodeCacher = "Emby Episode Cacher";
public const string RecentlyAddedEmail = "Recently Added Email Notification"; public const string RecentlyAddedEmail = "Recently Added Email Notification";
public const string FaultQueueHandler = "Request Fault Queue Handler"; public const string FaultQueueHandler = "Request Fault Queue Handler";
public const string PlexUserChecker = "Plex User Checker"; public const string PlexUserChecker = "Plex User Checker";
public const string EmbyUserChecker = "Emby User Checker";
} }
} }

@ -51,7 +51,7 @@ namespace Ombi.Services.Jobs
public class PlexAvailabilityChecker : IJob, IAvailabilityChecker public class PlexAvailabilityChecker : IJob, IAvailabilityChecker
{ {
public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache, public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache,
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo, INotificationEngine e, IRepository<PlexContent> content) INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo, IPlexNotificationEngine e, IRepository<PlexContent> content)
{ {
Plex = plexSettings; Plex = plexSettings;
RequestService = request; RequestService = request;
@ -82,6 +82,11 @@ namespace Ombi.Services.Jobs
var plexSettings = Plex.GetSettings(); var plexSettings = Plex.GetSettings();
if (!plexSettings.Enable)
{
return;
}
if (!ValidateSettings(plexSettings)) if (!ValidateSettings(plexSettings))
{ {
Log.Debug("Validation of the plex settings failed."); Log.Debug("Validation of the plex settings failed.");
@ -152,7 +157,7 @@ namespace Ombi.Services.Jobs
if (modifiedModel.Any()) if (modifiedModel.Any())
{ {
NotificationEngine.NotifyUsers(modifiedModel, plexSettings.PlexAuthToken, NotificationType.RequestAvailable); NotificationEngine.NotifyUsers(modifiedModel, NotificationType.RequestAvailable);
RequestService.BatchUpdate(modifiedModel); RequestService.BatchUpdate(modifiedModel);
} }
} }
@ -388,7 +393,7 @@ namespace Ombi.Services.Jobs
currentItem.RatingKey); currentItem.RatingKey);
// We do not want "all episodes" this as a season // We do not want "all episodes" this as a season
var filtered = seasons.Directory.Where( x => !x.Title.Equals("All episodes", StringComparison.CurrentCultureIgnoreCase)); var filtered = seasons.Directory.Where(x => !x.Title.Equals("All episodes", StringComparison.CurrentCultureIgnoreCase));
t1.Seasons.AddRange(filtered); t1.Seasons.AddRange(filtered);
} }
@ -447,12 +452,15 @@ namespace Ombi.Services.Jobs
private bool ValidateSettings(PlexSettings plex) private bool ValidateSettings(PlexSettings plex)
{ {
if (plex?.Ip == null || plex?.PlexAuthToken == null) if (plex.Enable)
{ {
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token."); if (plex?.Ip == null || plex?.PlexAuthToken == null)
return false; {
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
return false;
}
} }
return true; return plex.Enable;
} }
public void Execute(IJobExecutionContext context) public void Execute(IJobExecutionContext context)

@ -48,7 +48,7 @@ namespace Ombi.Services.Jobs
public class PlexContentCacher : IJob, IPlexContentCacher public class PlexContentCacher : IJob, IPlexContentCacher
{ {
public PlexContentCacher(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache, public PlexContentCacher(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache,
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo, INotificationEngine e, IRepository<PlexContent> content) INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo, IPlexNotificationEngine e, IRepository<PlexContent> content)
{ {
Plex = plexSettings; Plex = plexSettings;
RequestService = request; RequestService = request;
@ -77,7 +77,10 @@ namespace Ombi.Services.Jobs
public void CacheContent() public void CacheContent()
{ {
var plexSettings = Plex.GetSettings(); var plexSettings = Plex.GetSettings();
if (!plexSettings.Enable)
{
return;
}
if (!ValidateSettings(plexSettings)) if (!ValidateSettings(plexSettings))
{ {
Log.Debug("Validation of the plex settings failed."); Log.Debug("Validation of the plex settings failed.");
@ -385,12 +388,15 @@ namespace Ombi.Services.Jobs
private bool ValidateSettings(PlexSettings plex) private bool ValidateSettings(PlexSettings plex)
{ {
if (plex?.Ip == null || plex?.PlexAuthToken == null) if (plex.Enable)
{ {
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token."); if (plex?.Ip == null || plex?.PlexAuthToken == null)
return false; {
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
return false;
}
} }
return true; return plex.Enable;
} }
public void Execute(IJobExecutionContext context) public void Execute(IJobExecutionContext context)

@ -38,8 +38,10 @@ using Ombi.Core.SettingModels;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Services.Interfaces; using Ombi.Services.Interfaces;
using Ombi.Store.Models; using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using Quartz; using Quartz;
using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType;
namespace Ombi.Services.Jobs namespace Ombi.Services.Jobs
{ {

@ -37,6 +37,7 @@ using Ombi.Core.Users;
using Ombi.Helpers.Permissions; using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces; using Ombi.Services.Interfaces;
using Ombi.Store.Models; using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using Quartz; using Quartz;
@ -46,7 +47,7 @@ namespace Ombi.Services.Jobs
{ {
private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public PlexUserChecker(IPlexUserRepository plexUsers, IPlexApi plexAPi, IJobRecord rec, ISettingsService<PlexSettings> plexSettings, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<UserManagementSettings> umSettings, public PlexUserChecker(IExternalUserRepository<PlexUsers> plexUsers, IPlexApi plexAPi, IJobRecord rec, ISettingsService<PlexSettings> plexSettings, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<UserManagementSettings> umSettings,
IRequestService requestService, IUserRepository localUser) IRequestService requestService, IUserRepository localUser)
{ {
Repo = plexUsers; Repo = plexUsers;
@ -61,7 +62,7 @@ namespace Ombi.Services.Jobs
private IJobRecord JobRecord { get; } private IJobRecord JobRecord { get; }
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
private IPlexUserRepository Repo { get; } private IExternalUserRepository<PlexUsers> Repo { get; }
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; } private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings { get; } private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
@ -75,7 +76,7 @@ namespace Ombi.Services.Jobs
try try
{ {
var settings = PlexSettings.GetSettings(); var settings = PlexSettings.GetSettings();
if (string.IsNullOrEmpty(settings.PlexAuthToken)) if (string.IsNullOrEmpty(settings.PlexAuthToken) || !settings.Enable)
{ {
return; return;
} }

@ -0,0 +1,358 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RecentlyAddedModel.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Services.Jobs.Templates;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
using TMDbLib.Objects.Exceptions;
using EmbyMediaType = Ombi.Store.Models.Plex.EmbyMediaType;
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public class EmbyAddedNewsletter : HtmlTemplateGenerator, IEmbyAddedNewsletter
{
public EmbyAddedNewsletter(IEmbyApi api, ISettingsService<EmbySettings> embySettings,
ISettingsService<EmailNotificationSettings> email,
ISettingsService<NewletterSettings> newsletter, IRepository<RecentlyAddedLog> log,
IRepository<EmbyContent> embyContent, IRepository<EmbyEpisodes> episodes)
{
Api = api;
EmbySettings = embySettings;
EmailSettings = email;
NewsletterSettings = newsletter;
Content = embyContent;
MovieApi = new TheMovieDbApi();
TvApi = new TvMazeApi();
Episodes = episodes;
RecentlyAddedLog = log;
}
private IEmbyApi Api { get; }
private TheMovieDbApi MovieApi { get; }
private TvMazeApi TvApi { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<NewletterSettings> NewsletterSettings { get; }
private IRepository<EmbyContent> Content { get; }
private IRepository<EmbyEpisodes> Episodes { get; }
private IRepository<RecentlyAddedLog> RecentlyAddedLog { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string GetNewsletterHtml(bool test)
{
try
{
return GetHtml(test);
}
catch (Exception e)
{
Log.Error(e);
return string.Empty;
}
}
private class EmbyRecentlyAddedModel
{
public EmbyInformation EmbyInformation { get; set; }
public EmbyContent EmbyContent { get; set; }
public List<EmbyEpisodeInformation> EpisodeInformation { get; set; }
}
private string GetHtml(bool test)
{
var sb = new StringBuilder();
var embySettings = EmbySettings.GetSettings();
var embyContent = Content.GetAll().ToList();
var series = embyContent.Where(x => x.Type == EmbyMediaType.Series).ToList();
var episodes = Episodes.GetAll().ToList();
var movie = embyContent.Where(x => x.Type == EmbyMediaType.Movie).ToList();
var recentlyAdded = RecentlyAddedLog.GetAll().ToList();
var firstRun = !recentlyAdded.Any();
var filteredMovies = movie.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList();
var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList();
var info = new List<EmbyRecentlyAddedModel>();
foreach (var m in filteredMovies)
{
var i = Api.GetInformation(m.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Movie,
embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri);
info.Add(new EmbyRecentlyAddedModel
{
EmbyInformation = i,
EmbyContent = m
});
}
GenerateMovieHtml(info, sb);
info.Clear();
foreach (var t in series)
{
var i = Api.GetInformation(t.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Series,
embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri);
var ep = filteredEp.Where(x => x.ParentId == t.EmbyId);
if (ep.Any())
{
var episodeList = new List<EmbyEpisodeInformation>();
foreach (var embyEpisodese in ep)
{
var epInfo = Api.GetInformation(embyEpisodese.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Episode,
embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri);
episodeList.Add(epInfo.EpisodeInformation);
}
info.Add(new EmbyRecentlyAddedModel
{
EmbyContent = t,
EmbyInformation = i,
EpisodeInformation = episodeList
});
}
}
GenerateTvHtml(info, sb);
var template = new RecentlyAddedTemplate();
var html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
if (!test || firstRun)
{
foreach (var a in filteredMovies)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.ProviderId,
AddedAt = DateTime.UtcNow
});
}
foreach (var a in filteredEp)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.ProviderId,
AddedAt = DateTime.UtcNow
});
}
}
var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray());
Log.Debug(escapedHtml);
return escapedHtml;
}
private void GenerateMovieHtml(IEnumerable<EmbyRecentlyAddedModel> recentlyAddedMovies, StringBuilder sb)
{
var movies = recentlyAddedMovies?.ToList() ?? new List<EmbyRecentlyAddedModel>();
if (!movies.Any())
{
return;
}
var orderedMovies = movies.OrderByDescending(x => x.EmbyContent.AddedAt).Select(x => x.EmbyInformation.MovieInformation).ToList();
sb.Append("<h1>New Movies:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var movie in orderedMovies)
{
// We have a try within a try so we can catch the rate limit without ending the loop (finally block)
try
{
try
{
var imdbId = movie.ProviderIds.Imdb;
var info = MovieApi.GetMovieInformation(imdbId).Result;
if (info == null)
{
throw new Exception($"Movie with Imdb id {imdbId} returned null from the MovieApi");
}
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/w500{info.BackdropPath}");
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
Header(sb, 3, $"{info.Title} {info.ReleaseDate?.ToString("yyyy") ?? string.Empty}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
}
catch (RequestLimitExceededException limit)
{
// We have hit a limit, we need to now wait.
Thread.Sleep(TimeSpan.FromSeconds(10));
Log.Info(limit);
}
}
catch (Exception e)
{
Log.Error(e);
Log.Error("Error for movie with IMDB Id = {0}", movie.ProviderIds.Imdb);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private class TvModel
{
public EmbySeriesInformation Series { get; set; }
public List<EmbyEpisodeInformation> Episodes { get; set; }
}
private void GenerateTvHtml(IEnumerable<EmbyRecentlyAddedModel> recenetlyAddedTv, StringBuilder sb)
{
var tv = recenetlyAddedTv?.ToList() ?? new List<EmbyRecentlyAddedModel>();
if (!tv.Any())
{
return;
}
var orderedTv = tv.OrderByDescending(x => x.EmbyContent.AddedAt).ToList();
// TV
sb.Append("<h1>New Episodes:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv)
{
var seriesItem = t.EmbyInformation.SeriesInformation;
var relatedEpisodes = t.EpisodeInformation;
try
{
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(seriesItem.ProviderIds.Tvdb));
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
banner = banner.Replace("http", "https"); // Always use the Https banners
}
AddImageInsideTable(sb, banner);
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
var title = $"{seriesItem.Name} {seriesItem.PremiereDate.Year}";
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
Header(sb, 3, title);
EndTag(sb, "a");
var results = relatedEpisodes.GroupBy(p => p.ParentIndexNumber,
(key, g) => new
{
ParentIndexNumber = key,
IndexNumber = g.ToList()
}
);
// Group the episodes
foreach (var embyEpisodeInformation in results.OrderBy(x => x.ParentIndexNumber))
{
var epSb = new StringBuilder();
for (var i = 0; i < embyEpisodeInformation.IndexNumber.Count; i++)
{
var ep = embyEpisodeInformation.IndexNumber[i];
if (i < embyEpisodeInformation.IndexNumber.Count)
{
epSb.Append($"{ep.IndexNumber},");
}
else
{
epSb.Append(ep);
}
}
AddParagraph(sb, $"Season: {embyEpisodeInformation.ParentIndexNumber}, Episode: {epSb}");
}
if (info.genres.Any())
{
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
AddParagraph(sb, string.IsNullOrEmpty(seriesItem.Overview) ? info.summary : seriesItem.Overview);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private void EndLoopHtml(StringBuilder sb)
{
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
sb.Append("<hr />");
sb.Append("<br />");
sb.Append("<br />");
sb.Append("</td>");
sb.Append("</tr>");
}
}
}

@ -0,0 +1,7 @@
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public interface IEmbyAddedNewsletter
{
string GetNewsletterHtml(bool test);
}
}

@ -46,14 +46,15 @@ using Ombi.Services.Interfaces;
using Ombi.Services.Jobs.Templates; using Ombi.Services.Jobs.Templates;
using Quartz; using Quartz;
namespace Ombi.Services.Jobs namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{ {
public class RecentlyAdded : HtmlTemplateGenerator, IJob, IRecentlyAdded public class RecentlyAddedNewsletter : HtmlTemplateGenerator, IJob, IRecentlyAdded, IMassEmail
{ {
public RecentlyAdded(IPlexApi api, ISettingsService<PlexSettings> plexSettings, public RecentlyAddedNewsletter(IPlexApi api, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmailNotificationSettings> email, IJobRecord rec, ISettingsService<EmailNotificationSettings> email, IJobRecord rec,
ISettingsService<NewletterSettings> newsletter, ISettingsService<NewletterSettings> newsletter,
IPlexReadOnlyDatabase db, IUserHelper userHelper) IPlexReadOnlyDatabase db, IUserHelper userHelper, IEmbyAddedNewsletter embyNews,
ISettingsService<EmbySettings> embyS)
{ {
JobRecord = rec; JobRecord = rec;
Api = api; Api = api;
@ -62,23 +63,25 @@ namespace Ombi.Services.Jobs
NewsletterSettings = newsletter; NewsletterSettings = newsletter;
PlexDb = db; PlexDb = db;
UserHelper = userHelper; UserHelper = userHelper;
EmbyNewsletter = embyNews;
EmbySettings = embyS;
} }
private IPlexApi Api { get; } private IPlexApi Api { get; }
private TvMazeApi TvApi = new TvMazeApi(); private TvMazeApi TvApi = new TvMazeApi();
private readonly TheMovieDbApi _movieApi = new TheMovieDbApi(); private readonly TheMovieDbApi _movieApi = new TheMovieDbApi();
private const int MetadataTypeTv = 4;
private const int MetadataTypeMovie = 1;
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISettingsService<EmailNotificationSettings> EmailSettings { get; } private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<NewletterSettings> NewsletterSettings { get; } private ISettingsService<NewletterSettings> NewsletterSettings { get; }
private IJobRecord JobRecord { get; } private IJobRecord JobRecord { get; }
private IPlexReadOnlyDatabase PlexDb { get; } private IPlexReadOnlyDatabase PlexDb { get; }
private IUserHelper UserHelper { get; } private IUserHelper UserHelper { get; }
private IEmbyAddedNewsletter EmbyNewsletter { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public void Start() public void StartNewsLetter()
{ {
try try
{ {
@ -88,7 +91,7 @@ namespace Ombi.Services.Jobs
return; return;
} }
JobRecord.SetRunning(true, JobNames.RecentlyAddedEmail); JobRecord.SetRunning(true, JobNames.RecentlyAddedEmail);
Start(settings); StartNewsLetter(settings);
} }
catch (Exception e) catch (Exception e)
{ {
@ -102,114 +105,141 @@ namespace Ombi.Services.Jobs
} }
public void Execute(IJobExecutionContext context) public void Execute(IJobExecutionContext context)
{ {
Start(); StartNewsLetter();
} }
public void Test() public void RecentlyAddedAdminTest()
{ {
Log.Debug("Starting Test Newsletter"); Log.Debug("Starting Recently Added Newsletter Test");
var settings = NewsletterSettings.GetSettings(); var settings = NewsletterSettings.GetSettings();
Start(settings, true); StartNewsLetter(settings, true);
} }
private void Start(NewletterSettings newletterSettings, bool testEmail = false) public void MassEmailAdminTest(string html, string subject)
{ {
var sb = new StringBuilder(); Log.Debug("Starting Mass Email Test");
var plexSettings = PlexSettings.GetSettings(); var template = new MassEmailTemplate();
Log.Debug("Got Plex Settings"); var body = template.LoadTemplate(html);
SendMassEmail(body, subject, true);
var libs = Api.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); }
Log.Debug("Getting Plex Library Sections");
var tvSections = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib public void SendMassEmail(string html, string subject)
Log.Debug("Filtered sections for TV"); {
var movieSection = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib Log.Debug("Starting Mass Email Test");
Log.Debug("Filtered sections for Movies"); var template = new MassEmailTemplate();
var body = template.LoadTemplate(html);
SendMassEmail(body, subject, false);
}
var plexVersion = Api.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri).Version; private void StartNewsLetter(NewletterSettings newletterSettings, bool testEmail = false)
{
var embySettings = EmbySettings.GetSettings();
if (embySettings.Enable)
{
var html = EmbyNewsletter.GetNewsletterHtml(testEmail);
var html = string.Empty; var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray());
if (plexVersion.StartsWith("1.3")) Log.Debug(escapedHtml);
SendNewsletter(newletterSettings, escapedHtml, testEmail, "New Content On Emby!");
}
else
{ {
var tvMetadata = new List<Metadata>(); var sb = new StringBuilder();
var movieMetadata = new List<Metadata>(); var plexSettings = PlexSettings.GetSettings();
foreach (var tvSection in tvSections) Log.Debug("Got Plex Settings");
var libs = Api.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri);
Log.Debug("Getting Plex Library Sections");
var tvSections = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib
Log.Debug("Filtered sections for TV");
var movieSection = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib
Log.Debug("Filtered sections for Movies");
var plexVersion = Api.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri).Version;
var html = string.Empty;
if (plexVersion.StartsWith("1.3"))
{ {
var item = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, var tvMetadata = new List<Metadata>();
tvSection?.Key); var movieMetadata = new List<Metadata>();
if (item?.MediaContainer?.Metadata != null) foreach (var tvSection in tvSections)
{ {
tvMetadata.AddRange(item?.MediaContainer?.Metadata); var item = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri,
tvSection?.Key);
if (item?.MediaContainer?.Metadata != null)
{
tvMetadata.AddRange(item?.MediaContainer?.Metadata);
}
} }
} Log.Debug("Got RecentlyAdded TV Shows");
Log.Debug("Got RecentlyAdded TV Shows"); foreach (var movie in movieSection)
foreach (var movie in movieSection)
{
var recentlyAddedMovies = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key);
if (recentlyAddedMovies?.MediaContainer?.Metadata != null)
{ {
movieMetadata.AddRange(recentlyAddedMovies?.MediaContainer?.Metadata); var recentlyAddedMovies = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key);
if (recentlyAddedMovies?.MediaContainer?.Metadata != null)
{
movieMetadata.AddRange(recentlyAddedMovies?.MediaContainer?.Metadata);
}
} }
Log.Debug("Got RecentlyAdded Movies");
Log.Debug("Started Generating Movie HTML");
GenerateMovieHtml(movieMetadata, plexSettings, sb);
Log.Debug("Finished Generating Movie HTML");
Log.Debug("Started Generating TV HTML");
GenerateTvHtml(tvMetadata, plexSettings, sb);
Log.Debug("Finished Generating TV HTML");
var template = new RecentlyAddedTemplate();
html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
} }
Log.Debug("Got RecentlyAdded Movies"); else
Log.Debug("Started Generating Movie HTML");
GenerateMovieHtml(movieMetadata, plexSettings, sb);
Log.Debug("Finished Generating Movie HTML");
Log.Debug("Started Generating TV HTML");
GenerateTvHtml(tvMetadata, plexSettings, sb);
Log.Debug("Finished Generating TV HTML");
var template = new RecentlyAddedTemplate();
html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
}
else
{
// Old API
var tvChild = new List<RecentlyAddedChild>();
var movieChild = new List<RecentlyAddedChild>();
foreach (var tvSection in tvSections)
{ {
var recentlyAddedTv = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, tvSection?.Key); // Old API
if (recentlyAddedTv?._children != null) var tvChild = new List<RecentlyAddedChild>();
var movieChild = new List<RecentlyAddedChild>();
foreach (var tvSection in tvSections)
{ {
tvChild.AddRange(recentlyAddedTv?._children); var recentlyAddedTv = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, tvSection?.Key);
if (recentlyAddedTv?._children != null)
{
tvChild.AddRange(recentlyAddedTv?._children);
}
} }
}
Log.Debug("Got RecentlyAdded TV Shows"); Log.Debug("Got RecentlyAdded TV Shows");
foreach (var movie in movieSection) foreach (var movie in movieSection)
{
var recentlyAddedMovies = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key);
if (recentlyAddedMovies?._children != null)
{ {
tvChild.AddRange(recentlyAddedMovies?._children); var recentlyAddedMovies = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key);
if (recentlyAddedMovies?._children != null)
{
tvChild.AddRange(recentlyAddedMovies?._children);
}
} }
Log.Debug("Got RecentlyAdded Movies");
Log.Debug("Started Generating Movie HTML");
GenerateMovieHtml(movieChild, plexSettings, sb);
Log.Debug("Finished Generating Movie HTML");
Log.Debug("Started Generating TV HTML");
GenerateTvHtml(tvChild, plexSettings, sb);
Log.Debug("Finished Generating TV HTML");
var template = new RecentlyAddedTemplate();
html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
} }
Log.Debug("Got RecentlyAdded Movies"); string escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray());
Log.Debug(escapedHtml);
Log.Debug("Started Generating Movie HTML"); SendNewsletter(newletterSettings, escapedHtml, testEmail);
GenerateMovieHtml(movieChild, plexSettings, sb);
Log.Debug("Finished Generating Movie HTML");
Log.Debug("Started Generating TV HTML");
GenerateTvHtml(tvChild, plexSettings, sb);
Log.Debug("Finished Generating TV HTML");
var template = new RecentlyAddedTemplate();
html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
} }
Send(newletterSettings, html, plexSettings, testEmail);
} }
private void GenerateMovieHtml(List<RecentlyAddedChild> movies, PlexSettings plexSettings, StringBuilder sb) private void GenerateMovieHtml(List<RecentlyAddedChild> movies, PlexSettings plexSettings, StringBuilder sb)
{ {
var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List<RecentlyAddedChild>(); var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List<RecentlyAddedChild>();
sb.Append("<h1>New Movies:</h1><br/><br/>"); sb.Append("<h1>New Movies:</h1><br /><br />");
sb.Append( sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">"); "<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var movie in orderedMovies) foreach (var movie in orderedMovies)
@ -259,13 +289,13 @@ namespace Ombi.Services.Jobs
} }
} }
sb.Append("</table><br/><br/>"); sb.Append("</table><br /><br />");
} }
private void GenerateMovieHtml(List<Metadata> movies, PlexSettings plexSettings, StringBuilder sb) private void GenerateMovieHtml(List<Metadata> movies, PlexSettings plexSettings, StringBuilder sb)
{ {
var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List<Metadata>(); var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List<Metadata>();
sb.Append("<h1>New Movies:</h1><br/><br/>"); sb.Append("<h1>New Movies:</h1><br /><br />");
sb.Append( sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">"); "<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var movie in orderedMovies) foreach (var movie in orderedMovies)
@ -315,14 +345,14 @@ namespace Ombi.Services.Jobs
} }
} }
sb.Append("</table><br/><br/>"); sb.Append("</table><br /><br />");
} }
private void GenerateTvHtml(List<RecentlyAddedChild> tv, PlexSettings plexSettings, StringBuilder sb) private void GenerateTvHtml(List<RecentlyAddedChild> tv, PlexSettings plexSettings, StringBuilder sb)
{ {
var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList(); var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList();
// TV // TV
sb.Append("<h1>New Episodes:</h1><br/><br/>"); sb.Append("<h1>New Episodes:</h1><br /><br />");
sb.Append( sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">"); "<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv) foreach (var t in orderedTv)
@ -375,14 +405,14 @@ namespace Ombi.Services.Jobs
EndLoopHtml(sb); EndLoopHtml(sb);
} }
} }
sb.Append("</table><br/><br/>"); sb.Append("</table><br /><br />");
} }
private void GenerateTvHtml(List<Metadata> tv, PlexSettings plexSettings, StringBuilder sb) private void GenerateTvHtml(List<Metadata> tv, PlexSettings plexSettings, StringBuilder sb)
{ {
var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList(); var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList();
// TV // TV
sb.Append("<h1>New Episodes:</h1><br/><br/>"); sb.Append("<h1>New Episodes:</h1><br /><br />");
sb.Append( sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">"); "<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv) foreach (var t in orderedTv)
@ -435,12 +465,52 @@ namespace Ombi.Services.Jobs
EndLoopHtml(sb); EndLoopHtml(sb);
} }
} }
sb.Append("</table><br/><br/>"); sb.Append("</table><br /><br />");
}
private void SendMassEmail(string html, string subject, bool testEmail)
{
var settings = EmailSettings.GetSettings();
if (!settings.Enabled || string.IsNullOrEmpty(settings.EmailHost))
{
return;
}
var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." };
var message = new MimeMessage
{
Body = body.ToMessageBody(),
Subject = subject
};
Log.Debug("Created Plain/HTML MIME body");
if (!testEmail)
{
var users = UserHelper.GetUsers(); // Get all users
if (users != null)
{
foreach (var user in users)
{
if (!string.IsNullOrEmpty(user.EmailAddress))
{
message.Bcc.Add(new MailboxAddress(user.Username, user.EmailAddress)); // BCC everyone
}
}
}
}
message.Bcc.Add(new MailboxAddress(settings.EmailUsername, settings.RecipientEmail)); // Include the admin
message.From.Add(new MailboxAddress(settings.EmailUsername, settings.EmailSender));
SendMail(settings, message);
} }
private void Send(NewletterSettings newletterSettings, string html, PlexSettings plexSettings, bool testEmail = false) // TODO Emby
private void SendNewsletter(NewletterSettings newletterSettings, string html, bool testEmail = false, string subject = "New Content on Plex!")
{ {
Log.Debug("Entering Send"); Log.Debug("Entering SendNewsletter");
var settings = EmailSettings.GetSettings(); var settings = EmailSettings.GetSettings();
if (!settings.Enabled || string.IsNullOrEmpty(settings.EmailHost)) if (!settings.Enabled || string.IsNullOrEmpty(settings.EmailHost))
@ -453,7 +523,7 @@ namespace Ombi.Services.Jobs
var message = new MimeMessage var message = new MimeMessage
{ {
Body = body.ToMessageBody(), Body = body.ToMessageBody(),
Subject = "New Content on Plex!", Subject = subject
}; };
Log.Debug("Created Plain/HTML MIME body"); Log.Debug("Created Plain/HTML MIME body");
@ -487,6 +557,11 @@ namespace Ombi.Services.Jobs
message.Bcc.Add(new MailboxAddress(settings.EmailUsername, settings.RecipientEmail)); // Include the admin message.Bcc.Add(new MailboxAddress(settings.EmailUsername, settings.RecipientEmail)); // Include the admin
message.From.Add(new MailboxAddress(settings.EmailUsername, settings.EmailSender)); message.From.Add(new MailboxAddress(settings.EmailUsername, settings.EmailSender));
SendMail(settings, message);
}
private void SendMail(EmailNotificationSettings settings, MimeMessage message)
{
try try
{ {
using (var client = new SmtpClient()) using (var client = new SmtpClient())
@ -516,10 +591,12 @@ namespace Ombi.Services.Jobs
private void EndLoopHtml(StringBuilder sb) private void EndLoopHtml(StringBuilder sb)
{ {
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
sb.Append("<hr />");
sb.Append("<br />");
sb.Append("<br />");
sb.Append("</td>"); sb.Append("</td>");
sb.Append("<hr>");
sb.Append("<br>");
sb.Append("<br>");
sb.Append("</tr>"); sb.Append("</tr>");
} }

@ -0,0 +1,58 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RecentlyAddedTemplate.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.IO;
using System.Text;
using System.Windows.Forms;
using NLog;
namespace Ombi.Services.Jobs.Templates
{
public class MassEmailTemplate
{
public string TemplateLocation => Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, "Jobs", "Templates", "MassEmailTemplate.html");
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private const string RecentlyAddedKey = "{@MASSEMAIL}";
public string LoadTemplate(string html)
{
try
{
var sb = new StringBuilder(File.ReadAllText(TemplateLocation));
sb.Replace(RecentlyAddedKey, html);
return sb.ToString();
}
catch (Exception e)
{
Log.Error(e);
return string.Empty;
}
}
}
}

@ -0,0 +1,181 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Ombi</title>
<style media="all" type="text/css">
@media all {
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}
@media all {
.btn-secondary a:hover {
border-color: #34495e !important;
color: #34495e !important;
}
}
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] h2 {
font-size: 22px !important;
margin-bottom: 10px !important;
}
table[class=body] h3 {
font-size: 16px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .header {
margin-bottom: 10px !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
table[class=body] .alert td {
border-radius: 0 !important;
padding: 10px !important;
}
table[class=body] .span-2,
table[class=body] .span-3 {
max-width: none !important;
width: 100% !important;
}
table[class=body] .receipt {
width: 100% !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
}
</style>
</head>
<body class="" style="font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background-color: #f6f6f6; margin: 0; padding: 0;">
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #f6f6f6;" width="100%" bgcolor="#f6f6f6">
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto !important; max-width: 580px; padding: 10px; width: 580px;" width="580" valign="top">
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ombi Recently Added</span>
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #fff; border-radius: 3px;" width="100%">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td align="center">
<img src="http://i.imgur.com/qQsN78U.png" width="400px" text-align="center" />
</td>
</tr>
<tr>
<td align="left">
{@MASSEMAIL}
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer" style="clear: both; padding-top: 10px; text-align: center; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center">
Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a>
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
</tr>
</table>
</body>
</html>

@ -144,14 +144,14 @@
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%"> <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr> <tr>
<td align="center"> <td align="center">
<img src="http://i.imgur.com/ROTp8mn.png" text-align="center" /> <img src="http://i.imgur.com/qQsN78U.png" width="400px" text-align="center" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> <td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
<br/> <br />
<br/> <br />
<p style="font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Here is a list of Movies and TV Shows that have recently been added to Plex!</p> <p style="font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Here is a list of Movies and TV Shows that have recently been added!</p>
</td> </td>
</tr> </tr>

@ -70,7 +70,7 @@ namespace Ombi.Services.Jobs
{ {
if (watcherSettings.Enabled) if (watcherSettings.Enabled)
{ {
var movies = WatcherApi.ListMovies(watcherSettings.ApiKey, watcherSettings.FullUri); var movies = WatcherApi.ListMovies(watcherSettings.ApiKey, watcherSettings.FullUri);
if (movies.Error) if (movies.Error)
{ {
Log.Error("Error when trying to get Watchers movies"); Log.Error("Error when trying to get Watchers movies");

@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Core.Users;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
namespace Ombi.Services.Notification
{
public class EmbyNotificationEngine : IEmbyNotificationEngine
{
public EmbyNotificationEngine(IEmbyApi p, IRepository<UsersToNotify> repo, ISettingsService<EmbySettings> embySettings, INotificationService service, IUserHelper userHelper, IExternalUserRepository<EmbyUsers> embyUsers)
{
EmbyApi = p;
UserNotifyRepo = repo;
Notification = service;
UserHelper = userHelper;
EmbySettings = embySettings;
EmbyUserRepo = embyUsers;
}
private IEmbyApi EmbyApi { get; }
private IRepository<UsersToNotify> UserNotifyRepo { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private INotificationService Notification { get; }
private IUserHelper UserHelper { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IExternalUserRepository<EmbyUsers> EmbyUserRepo { get; }
public async Task NotifyUsers(IEnumerable<RequestedModel> modelChanged, NotificationType type)
{
try
{
var embySettings = await EmbySettings.GetSettingsAsync();
var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey);
var userAccount = embyUsers.FirstOrDefault(x => x.Policy.IsAdministrator);
var adminUsername = userAccount?.Name ?? string.Empty;
var users = UserHelper.GetUsersWithFeature(Features.RequestAddedNotification).ToList();
Log.Debug("Notifying Users Count {0}", users.Count);
foreach (var model in modelChanged)
{
var selectedUsers = new List<string>();
foreach (var u in users)
{
var requestUser = model.RequestedUsers.FirstOrDefault(
x => x.Equals(u.Username, StringComparison.CurrentCultureIgnoreCase) || x.Equals(u.UserAlias, StringComparison.CurrentCultureIgnoreCase));
if (string.IsNullOrEmpty(requestUser))
{
continue;
}
// Make sure we do not already have the user
if (!selectedUsers.Contains(requestUser))
{
selectedUsers.Add(requestUser);
}
}
foreach (var user in selectedUsers)
{
var localUser =
users.FirstOrDefault(x =>
x.Username.Equals(user, StringComparison.CurrentCultureIgnoreCase) ||
x.UserAlias.Equals(user, StringComparison.CurrentCultureIgnoreCase));
Log.Info("Notifying user {0}", user);
if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase))
{
Log.Info("This user is the Plex server owner");
await PublishUserNotification(userAccount?.Name, localUser?.EmailAddress, model.Title, model.PosterPath, type, model.Type);
return;
}
// So if the request was from an alias, then we need to use the local user (since that contains the alias).
// If we do not have a local user, then we should be using the Emby user if that user exists.
// This will execute most of the time since Emby and Local users will most always be in the database.
if (localUser != null)
{
if (string.IsNullOrEmpty(localUser?.EmailAddress))
{
Log.Info("There is no email address for this Local user ({0}), cannot send notification", localUser.Username);
continue;
}
Log.Info("Sending notification to: {0} at: {1}, for : {2}", localUser, localUser.EmailAddress, model.Title);
await PublishUserNotification(localUser.Username, localUser.EmailAddress, model.Title, model.PosterPath, type, model.Type);
}
else
{
var embyUser = EmbyUserRepo.GetUserByUsername(user);
var email = embyUsers.FirstOrDefault(x => x.Name.Equals(user, StringComparison.CurrentCultureIgnoreCase));
if (string.IsNullOrEmpty(embyUser?.EmailAddress)) // TODO this needs to be the email
{
Log.Info("There is no email address for this Emby user ({0}), cannot send notification", email?.Name);
// We do not have a plex user that requested this!
continue;
}
Log.Info("Sending notification to: {0} at: {1}, for : {2}", embyUser?.Username, embyUser?.EmailAddress, model.Title);
await PublishUserNotification(email?.Name, embyUser?.EmailAddress, model.Title, model.PosterPath, type, model.Type);
}
}
}
}
catch (Exception e)
{
Log.Error(e);
}
}
public async Task NotifyUsers(RequestedModel model, NotificationType type)
{
try
{
var embySettings = await EmbySettings.GetSettingsAsync();
var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey);
var userAccount = embyUsers.FirstOrDefault(x => x.Policy.IsAdministrator);
var localUsers = UserHelper.GetUsers().ToList();
var adminUsername = userAccount.Name ?? string.Empty;
var users = UserHelper.GetUsersWithFeature(Features.RequestAddedNotification).ToList();
Log.Debug("Notifying Users Count {0}", users.Count);
// Get the usernames or alias depending if they have an alias
var userNamesWithFeature = users.Select(x => x.UsernameOrAlias).ToList();
Log.Debug("Users with the feature count {0}", userNamesWithFeature.Count);
Log.Debug("Usernames: ");
foreach (var u in userNamesWithFeature)
{
Log.Debug(u);
}
Log.Debug("Users in the requested model count: {0}", model.AllUsers.Count);
Log.Debug("usernames from model: ");
foreach (var modelAllUser in model.AllUsers)
{
Log.Debug(modelAllUser);
}
if (model.AllUsers == null || !model.AllUsers.Any())
{
Log.Debug("There are no users in the model.AllUsers, no users to notify");
return;
}
var usersToNotify = userNamesWithFeature.Intersect(model.AllUsers, StringComparer.CurrentCultureIgnoreCase).ToList();
if (!usersToNotify.Any())
{
Log.Debug("Could not find any users after the .Intersect()");
}
Log.Debug("Users being notified for this request count {0}", users.Count);
foreach (var user in usersToNotify)
{
var embyUser = EmbyUserRepo.GetUserByUsername(user);
Log.Info("Notifying user {0}", user);
if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase))
{
Log.Info("This user is the Emby server owner");
await PublishUserNotification(userAccount.Name, embyUser.EmailAddress, model.Title, model.PosterPath, type, model.Type);
return;
}
var email = embyUsers.FirstOrDefault(x => x.Name.Equals(user, StringComparison.CurrentCultureIgnoreCase));
if (email == null)
{
// Local User?
var local = localUsers.FirstOrDefault(x => x.UsernameOrAlias.Equals(user));
if (local != null)
{
Log.Info("Sending notification to: {0} at: {1}, for title: {2}", local.UsernameOrAlias, local.EmailAddress, model.Title);
await PublishUserNotification(local.UsernameOrAlias, local.EmailAddress, model.Title, model.PosterPath, type, model.Type);
continue;
}
}
Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Name, embyUser.EmailAddress, model.Title);
await PublishUserNotification(email.Name, embyUser.EmailAddress, model.Title, model.PosterPath, type, model.Type);
}
}
catch (Exception e)
{
Log.Error(e);
}
}
private async Task PublishUserNotification(string username, string email, string title, string img, NotificationType type, RequestType requestType)
{
var notificationModel = new NotificationModel
{
User = username,
UserEmail = email,
NotificationType = type,
Title = title,
ImgSrc = requestType == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{img}" : img
};
// Send the notification to the user.
await Notification.Publish(notificationModel);
}
}
}

@ -1,7 +1,7 @@
#region Copyright #region Copyright
// /************************************************************************ // /************************************************************************
// Copyright (c) 2016 Jamie Rees // Copyright (c) 2016 Jamie Rees
// File: NotificationEngine.cs // File: PlexNotificationEngine.cs
// Created By: Jamie Rees // Created By: Jamie Rees
// //
// Permission is hereby granted, free of charge, to any person obtaining // Permission is hereby granted, free of charge, to any person obtaining
@ -31,7 +31,9 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using Ombi.Api.Interfaces; using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.Models; using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Core.Users; using Ombi.Core.Users;
using Ombi.Helpers.Permissions; using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces; using Ombi.Services.Interfaces;
@ -41,14 +43,15 @@ using Ombi.Store.Repository;
namespace Ombi.Services.Notification namespace Ombi.Services.Notification
{ {
public class NotificationEngine : INotificationEngine public class PlexNotificationEngine : IPlexNotificationEngine
{ {
public NotificationEngine(IPlexApi p, IRepository<UsersToNotify> repo, INotificationService service, IUserHelper userHelper) public PlexNotificationEngine(IPlexApi p, IRepository<UsersToNotify> repo, INotificationService service, IUserHelper userHelper, ISettingsService<PlexSettings> ps)
{ {
PlexApi = p; PlexApi = p;
UserNotifyRepo = repo; UserNotifyRepo = repo;
Notification = service; Notification = service;
UserHelper = userHelper; UserHelper = userHelper;
PlexSettings = ps;
} }
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
@ -56,13 +59,15 @@ namespace Ombi.Services.Notification
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private INotificationService Notification { get; } private INotificationService Notification { get; }
private IUserHelper UserHelper { get; } private IUserHelper UserHelper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
public async Task NotifyUsers(IEnumerable<RequestedModel> modelChanged, string apiKey, NotificationType type) public async Task NotifyUsers(IEnumerable<RequestedModel> modelChanged, NotificationType type)
{ {
try try
{ {
var plexUser = PlexApi.GetUsers(apiKey); var settings = await PlexSettings.GetSettingsAsync();
var userAccount = PlexApi.GetAccount(apiKey); var plexUser = PlexApi.GetUsers(settings.PlexAuthToken);
var userAccount = PlexApi.GetAccount(settings.PlexAuthToken);
var adminUsername = userAccount.Username ?? string.Empty; var adminUsername = userAccount.Username ?? string.Empty;
@ -161,12 +166,15 @@ namespace Ombi.Services.Notification
} }
} }
public async Task NotifyUsers(RequestedModel model, string apiKey, NotificationType type) public async Task NotifyUsers(RequestedModel model, NotificationType type)
{ {
try try
{ {
var plexUser = PlexApi.GetUsers(apiKey); var settings = await PlexSettings.GetSettingsAsync();
var userAccount = PlexApi.GetAccount(apiKey);
var plexUser = PlexApi.GetUsers(settings.PlexAuthToken); // TODO emby
var userAccount = PlexApi.GetAccount(settings.PlexAuthToken);
var localUsers = UserHelper.GetUsers().ToList();
var adminUsername = userAccount.Username ?? string.Empty; var adminUsername = userAccount.Username ?? string.Empty;
@ -213,11 +221,17 @@ namespace Ombi.Services.Notification
} }
var email = plexUser.User.FirstOrDefault(x => x.Username.Equals(user, StringComparison.CurrentCultureIgnoreCase)); var email = plexUser.User.FirstOrDefault(x => x.Username.Equals(user, StringComparison.CurrentCultureIgnoreCase));
if (email == null) if (email == null) // This is not a Plex User
{ {
Log.Info("There is no email address for this Plex user, cannot send notification"); // Local User?
// We do not have a plex user that requested this! var local = localUsers.FirstOrDefault(x => x.UsernameOrAlias.Equals(user));
continue; if (local != null)
{
Log.Info("Sending notification to: {0} at: {1}, for title: {2}", local.UsernameOrAlias, local.EmailAddress, model.Title);
await PublishUserNotification(local.UsernameOrAlias, local.EmailAddress, model.Title, model.PosterPath, type, model.Type);
continue;
}
} }
Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title); Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title);

@ -86,6 +86,9 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Interfaces\IEmbyNotificationEngine.cs" />
<Compile Include="Interfaces\IMassEmail.cs" />
<Compile Include="Interfaces\IPlexNotificationEngine.cs" />
<Compile Include="Interfaces\IRadarrCacher.cs" /> <Compile Include="Interfaces\IRadarrCacher.cs" />
<Compile Include="Interfaces\IWatcherCacher.cs" /> <Compile Include="Interfaces\IWatcherCacher.cs" />
<Compile Include="Interfaces\IJobRecord.cs" /> <Compile Include="Interfaces\IJobRecord.cs" />
@ -93,10 +96,21 @@
<Compile Include="Interfaces\IStoreBackup.cs" /> <Compile Include="Interfaces\IStoreBackup.cs" />
<Compile Include="Interfaces\IStoreCleanup.cs" /> <Compile Include="Interfaces\IStoreCleanup.cs" />
<Compile Include="Interfaces\IUserRequestLimitResetter.cs" /> <Compile Include="Interfaces\IUserRequestLimitResetter.cs" />
<Compile Include="Jobs\IFaultQueueHandler.cs" /> <Compile Include="Jobs\IEmbyUserChecker.cs" />
<Compile Include="Jobs\IPlexEpisodeCacher.cs" /> <Compile Include="Jobs\Interfaces\IEmbyEpisodeCacher.cs" />
<Compile Include="Jobs\IPlexUserChecker.cs" /> <Compile Include="Jobs\Interfaces\IEmbyContentCacher.cs" />
<Compile Include="Jobs\Interfaces\IEmbyAvailabilityChecker.cs" />
<Compile Include="Jobs\Interfaces\IFaultQueueHandler.cs" />
<Compile Include="Jobs\Interfaces\IPlexEpisodeCacher.cs" />
<Compile Include="Jobs\Interfaces\IPlexUserChecker.cs" />
<Compile Include="Jobs\EmbyAvailabilityChecker.cs" />
<Compile Include="Jobs\EmbyContentCacher.cs" />
<Compile Include="Jobs\EmbyEpisodeCacher.cs" />
<Compile Include="Jobs\EmbyUserChecker.cs" />
<Compile Include="Jobs\RadarrCacher.cs" /> <Compile Include="Jobs\RadarrCacher.cs" />
<Compile Include="Jobs\RecentlyAddedNewsletter\EmbyRecentlyAddedNewsletter.cs" />
<Compile Include="Jobs\RecentlyAddedNewsletter\IEmbyAddedNewsletter.cs" />
<Compile Include="Jobs\Templates\MassEmailTemplate.cs" />
<Compile Include="Jobs\WatcherCacher.cs" /> <Compile Include="Jobs\WatcherCacher.cs" />
<Compile Include="Jobs\HtmlTemplateGenerator.cs" /> <Compile Include="Jobs\HtmlTemplateGenerator.cs" />
<Compile Include="Interfaces\IPlexContentCacher.cs" /> <Compile Include="Interfaces\IPlexContentCacher.cs" />
@ -105,7 +119,7 @@
<Compile Include="Jobs\JobNames.cs" /> <Compile Include="Jobs\JobNames.cs" />
<Compile Include="Jobs\PlexContentCacher.cs" /> <Compile Include="Jobs\PlexContentCacher.cs" />
<Compile Include="Jobs\PlexEpisodeCacher.cs" /> <Compile Include="Jobs\PlexEpisodeCacher.cs" />
<Compile Include="Jobs\RecentlyAdded.cs" /> <Compile Include="Jobs\RecentlyAddedNewsletter\RecentlyAddedNewsletter.cs" />
<Compile Include="Jobs\StoreBackup.cs" /> <Compile Include="Jobs\StoreBackup.cs" />
<Compile Include="Jobs\PlexUserChecker.cs" /> <Compile Include="Jobs\PlexUserChecker.cs" />
<Compile Include="Jobs\StoreCleanup.cs" /> <Compile Include="Jobs\StoreCleanup.cs" />
@ -129,7 +143,8 @@
<Compile Include="Interfaces\INotificationService.cs" /> <Compile Include="Interfaces\INotificationService.cs" />
<Compile Include="Models\SonarrCachedResult.cs" /> <Compile Include="Models\SonarrCachedResult.cs" />
<Compile Include="Notification\EmailMessageNotification.cs" /> <Compile Include="Notification\EmailMessageNotification.cs" />
<Compile Include="Notification\NotificationEngine.cs" /> <Compile Include="Notification\EmbyNotificationEngine.cs" />
<Compile Include="Notification\PlexNotificationEngine.cs" />
<Compile Include="Notification\NotificationModel.cs" /> <Compile Include="Notification\NotificationModel.cs" />
<Compile Include="Notification\NotificationService.cs" /> <Compile Include="Notification\NotificationService.cs" />
<Compile Include="Notification\PushoverNotification.cs" /> <Compile Include="Notification\PushoverNotification.cs" />
@ -169,6 +184,9 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Jobs\Templates\MassEmailTemplate.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Jobs\Templates\RecentlyAddedTemplate.html"> <Content Include="Jobs\Templates\RecentlyAddedTemplate.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>

@ -0,0 +1,44 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: Emby.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using Dapper.Contrib.Extensions;
using Ombi.Store.Models.Plex;
namespace Ombi.Store.Models.Emby
{
[Table(nameof(EmbyContent))]
public class EmbyContent : Entity
{
public string Title { get; set; }
public string EmbyId { get; set; }
public DateTime PremierDate { get; set; }
public string ProviderId { get; set; }
public EmbyMediaType Type { get; set; }
public DateTime AddedAt { get; set; }
}
}

@ -0,0 +1,45 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: EmbyEpisodes.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using Dapper.Contrib.Extensions;
namespace Ombi.Store.Models.Emby
{
[Table(nameof(EmbyEpisodes))]
public class EmbyEpisodes : Entity
{
public string EpisodeTitle { get; set; }
public string ShowTitle { get; set; }
public string EmbyId { get; set; }
public int EpisodeNumber { get; set; }
public int SeasonNumber { get; set; }
public string ParentId { get; set; }
public string ProviderId { get; set; }
public DateTime AddedAt { get; set; }
}
}

@ -0,0 +1,35 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexMediaType .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.Store.Models.Plex
{
public enum EmbyMediaType
{
Movie = 0,
Series = 1,
Music = 2
}
}

@ -0,0 +1,43 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexUsers.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Dapper.Contrib.Extensions;
namespace Ombi.Store.Models.Emby
{
[Table(nameof(EmbyUsers))]
public class EmbyUsers : Entity
{
public string EmbyUserId { get; set; }
public string UserAlias { get; set; }
public int Permissions { get; set; }
public int Features { get; set; }
public string Username { get; set; }
public string EmailAddress { get; set; }
public string LoginId { get; set; }
}
}

@ -27,7 +27,7 @@
using Dapper.Contrib.Extensions; using Dapper.Contrib.Extensions;
namespace Ombi.Store.Models namespace Ombi.Store.Models.Plex
{ {
[Table("PlexEpisodes")] [Table("PlexEpisodes")]
public class PlexEpisodes : Entity public class PlexEpisodes : Entity

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

Loading…
Cancel
Save