using System; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models.Friends; using Ombi.Api.Plex.Models.OAuth; using Ombi.Api.Plex.Models.Server; using Ombi.Api.Plex.Models.Status; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.Settings.Settings.Models; namespace Ombi.Api.Plex { public class PlexApi : IPlexApi { public PlexApi(IApi api, ISettingsService settings, ISettingsService p) { Api = api; _custom = settings; _plexSettings = p; } private IApi Api { get; } private readonly ISettingsService _custom; private readonly ISettingsService _plexSettings; private string _app; private string ApplicationName { get { if (string.IsNullOrEmpty(_app)) { var settings = _custom.GetSettings(); if (settings.ApplicationName.IsNullOrEmpty()) { _app = "Ombi"; } else { // Check for non-ascii characters (New .Net Core HTTPLib does not allow this) var chars = settings.ApplicationName.ToCharArray(); var hasNonAscii = false; foreach (var c in chars) { if (c > 128) { hasNonAscii = true; } } _app = hasNonAscii ? "Ombi" : settings.ApplicationName; } return _app; } return _app; } } private const string SignInUri = "https://plex.tv/users/sign_in.json"; private const string FriendsUri = "https://plex.tv/pms/friends/all"; private const string GetAccountUri = "https://plex.tv/users/account.json"; private const string ServerUri = "https://plex.tv/pms/servers.xml"; private const string WatchlistUri = "https://metadata.provider.plex.tv/"; /// /// Sign into the Plex API /// This is for authenticating users credentials with Plex /// NOTE: Plex "Managed" users do not work /// /// /// public async Task SignIn(UserRequest user) { var userModel = new PlexUserRequest { user = user }; var request = new Request(SignInUri, string.Empty, HttpMethod.Post); await AddHeaders(request); request.AddJsonBody(userModel); var obj = await Api.Request(request); return obj; } public async Task GetStatus(string authToken, string uri) { var request = new Request(uri, string.Empty, HttpMethod.Get); await AddHeaders(request, authToken); return await Api.Request(request); } public async Task GetAccount(string authToken) { var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get); await AddHeaders(request, authToken); return await Api.Request(request); } public async Task GetServer(string authToken) { var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml); await AddHeaders(request, authToken); return await Api.Request(request); } public async Task GetLibrarySections(string authToken, string plexFullHost) { var request = new Request("library/sections", plexFullHost, HttpMethod.Get); await AddHeaders(request, authToken); return await Api.Request(request); } public async Task GetLibrary(string authToken, string plexFullHost, string libraryId) { var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get); request.AddQueryString("includeGuids","1"); await AddHeaders(request, authToken); return await Api.Request(request); } public async Task GetLibrariesForMachineId(string authToken, string machineId) { var request = new Request("", $"https://plex.tv/api/servers/{machineId}", HttpMethod.Get, ContentType.Xml); await AddHeaders(request, authToken); return await Api.Request(request); } /// /// 192.168.1.69:32400/library/metadata/3662/allLeaves /// The metadata ratingkey should be in the Cache /// Search for it and then call the above with the Directory.RatingKey /// THEN! We need the episode metadata using result.Vide.Key ("/library/metadata/3664") /// We then have the GUID which contains the TVDB ID plus the season and episode number: guid="com.plexapp.agents.thetvdb://269586/2/8?lang=en" /// /// /// /// public async Task GetEpisodeMetaData(string authToken, string plexFullHost, string ratingKey) { var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get); await AddHeaders(request, authToken); return await Api.Request(request); } public async Task GetMetadata(string authToken, string plexFullHost, string itemId) { var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get); await AddHeaders(request, authToken); return await Api.Request(request); } public async Task GetSeasons(string authToken, string plexFullHost, string ratingKey) { var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get); await AddHeaders(request, authToken); return await Api.Request(request); } /// /// Gets all episodes. /// /// The authentication token. /// The host. /// The section. /// The start count. /// The return count, how many items you want returned. /// public async Task GetAllEpisodes(string authToken, string host, string section, int start, int retCount) { var request = new Request($"/library/sections/{section}/all", host, HttpMethod.Get); request.AddQueryString("type", "4"); AddLimitHeaders(request, start, retCount); await AddHeaders(request, authToken); return await Api.Request(request); } /// /// Retuns all the Plex users for this account /// NOTE: For HOME USERS. There is no username or email, the user's home name is under the title property /// /// /// public async Task GetUsers(string authToken) { var request = new Request(string.Empty, FriendsUri, HttpMethod.Get, ContentType.Xml); await AddHeaders(request, authToken); return await Api.Request(request); } public async Task GetRecentlyAdded(string authToken, string uri, string sectionId) { var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get); await AddHeaders(request, authToken); AddLimitHeaders(request, 0, 50); return await Api.Request(request); } public async Task GetPin(int pinId) { var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get); await AddHeaders(request); var response = await Api.RequestContent(request); if (response.Contains("errors")) { var errors = JsonConvert.DeserializeObject(response, Ombi.Api.Api.Settings); return new OAuthContainer { Errors = errors }; } var pinResult = JsonConvert.DeserializeObject(response, Ombi.Api.Api.Settings); return new OAuthContainer { Result = pinResult }; } public async Task GetOAuthUrl(string code, string applicationUrl) { var request = new Request("auth/#", "https://app.plex.tv", HttpMethod.Get); await AddHeaders(request); request.AddQueryString("code", code); request.AddQueryString("context[device][product]", ApplicationName); request.AddQueryString("context[device][environment]", "bundled"); request.AddQueryString("context[device][layout]", "desktop"); request.AddQueryString("context[device][platform]", "Web"); request.AddQueryString("context[device][device]", "Ombi"); var s = await GetSettings(); await CheckInstallId(s); request.AddQueryString("clientID", s.InstallId.ToString("N")); if (request.FullUri.Fragment.Equals("#")) { var uri = request.FullUri.ToString(); var withoutEnd = uri.Remove(uri.Length - 1, 1); var startOfQueryLocation = withoutEnd.IndexOf('?'); var better = withoutEnd.Insert(startOfQueryLocation, "#"); request.FullUri = new Uri(better); } return request.FullUri; } public async Task AddUser(string emailAddress, string serverId, string authToken, int[] libs) { var request = new Request(string.Empty, $"https://plex.tv/api/servers/{serverId}/shared_servers", HttpMethod.Post, ContentType.Xml); await AddHeaders(request, authToken); request.AddJsonBody(new { server_id = serverId, shared_server = new { library_section_ids = libs.Length > 0 ? libs : new int[]{}, invited_email = emailAddress }, sharing_settings = new { } }); var result = await Api.RequestContent(request); try { var add = Api.DeserializeXml(result); return new PlexAddWrapper{Add = add}; } catch (InvalidOperationException) { var error = Api.DeserializeXml(result); return new PlexAddWrapper{Error = error}; } } public async Task GetWatchlist(string plexToken, CancellationToken cancellationToken) { var request = new Request("library/sections/watchlist/all", WatchlistUri, HttpMethod.Get); await AddHeaders(request, plexToken); var result = await Api.Request(request, cancellationToken); if (result.StatusCode.Equals(HttpStatusCode.Unauthorized)) { return new PlexWatchlistContainer { AuthError = true }; } var receivedString = await result.Content.ReadAsStringAsync(cancellationToken); return JsonConvert.DeserializeObject(receivedString); } public async Task GetWatchlistMetadata(string ratingKey, string plexToken, CancellationToken cancellationToken) { var request = new Request($"library/metadata/{ratingKey}", WatchlistUri, HttpMethod.Get); await AddHeaders(request, plexToken); var result = await Api.Request(request, cancellationToken); return result; } /// /// Adds the required headers and also the authorization header /// /// /// private async Task AddHeaders(Request request, string authToken) { request.AddHeader("X-Plex-Token", authToken); await AddHeaders(request); } /// /// Adds the main required headers to the Plex Request /// /// private async Task AddHeaders(Request request) { var s = await GetSettings(); await CheckInstallId(s); request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N")); request.AddHeader("X-Plex-Product", ApplicationName); request.AddHeader("X-Plex-Version", "3"); request.AddHeader("X-Plex-Device", "Ombi"); request.AddHeader("X-Plex-Platform", "Web"); request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); request.AddHeader("Accept", "application/json"); } private void AddLimitHeaders(Request request, int from, int to) { request.AddHeader("X-Plex-Container-Start", from.ToString()); request.AddHeader("X-Plex-Container-Size", to.ToString()); } private async Task CheckInstallId(PlexSettings s) { if (s?.InstallId == Guid.Empty || s.InstallId == Guid.Empty) { s.InstallId = Guid.NewGuid(); await _plexSettings.SaveSettingsAsync(s); } } private PlexSettings _settings; private async Task GetSettings() { return _settings ?? (_settings = await _plexSettings.GetSettingsAsync()); } } }