diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 2dd1a638f..9cf188981 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -24,5 +24,6 @@ namespace Ombi.Api.Plex Task GetRecentlyAdded(string authToken, string uri, string sectionId); Task GetPin(int pinId); Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); + Task AddUser(string emailAddress, string serverId, string authToken, int[] libs); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/PlexAdd.cs b/src/Ombi.Api.Plex/Models/PlexAdd.cs new file mode 100644 index 000000000..fb0a550d0 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/PlexAdd.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Ombi.Api.Plex.Models +{ + [XmlRoot(ElementName = "Section")] + public class Section + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + [XmlAttribute(AttributeName = "key")] + public string Key { get; set; } + [XmlAttribute(AttributeName = "title")] + public string Title { get; set; } + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + [XmlAttribute(AttributeName = "shared")] + public string Shared { get; set; } + } + + [XmlRoot(ElementName = "SharedServer")] + public class SharedServer + { + [XmlElement(ElementName = "Section")] + public List
Section { get; set; } + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + [XmlAttribute(AttributeName = "username")] + public string Username { get; set; } + [XmlAttribute(AttributeName = "email")] + public string Email { get; set; } + [XmlAttribute(AttributeName = "userID")] + public string UserID { get; set; } + [XmlAttribute(AttributeName = "accessToken")] + public string AccessToken { get; set; } + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + [XmlAttribute(AttributeName = "acceptedAt")] + public string AcceptedAt { get; set; } + [XmlAttribute(AttributeName = "invitedAt")] + public string InvitedAt { get; set; } + [XmlAttribute(AttributeName = "allowSync")] + public string AllowSync { get; set; } + [XmlAttribute(AttributeName = "allowCameraUpload")] + public string AllowCameraUpload { get; set; } + [XmlAttribute(AttributeName = "allowChannels")] + public string AllowChannels { get; set; } + [XmlAttribute(AttributeName = "allowTuners")] + public string AllowTuners { get; set; } + [XmlAttribute(AttributeName = "owned")] + public string Owned { get; set; } + } + + [XmlRoot(ElementName = "MediaContainer")] + public class PlexAdd + { + [XmlElement(ElementName = "SharedServer")] + public SharedServer SharedServer { get; set; } + [XmlAttribute(AttributeName = "friendlyName")] + public string FriendlyName { get; set; } + [XmlAttribute(AttributeName = "identifier")] + public string Identifier { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "size")] + public string Size { get; set; } + } + + [XmlRoot(ElementName = "Response")] + public class AddUserError + { + [XmlAttribute(AttributeName = "code")] + public string Code { get; set; } + [XmlAttribute(AttributeName = "status")] + public string Status { get; set; } + } + + public class PlexAddWrapper + { + public PlexAdd Add { get; set; } + public AddUserError Error { get; set; } + public bool HasError => Error != null; + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index f9de4f639..556d23b24 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -243,6 +243,34 @@ namespace Ombi.Api.Plex 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}; + } + } + + /// /// Adds the required headers and also the authorization header /// diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index b0e7066a8..19dab7530 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -80,15 +80,20 @@ namespace Ombi.Api else { // XML - XmlSerializer serializer = new XmlSerializer(typeof(T)); - StringReader reader = new StringReader(receivedString); - var value = (T)serializer.Deserialize(reader); - return value; + return DeserializeXml(receivedString); } } } + public T DeserializeXml(string receivedString) + { + XmlSerializer serializer = new XmlSerializer(typeof(T)); + StringReader reader = new StringReader(receivedString); + var value = (T) serializer.Deserialize(reader); + return value; + } + public async Task RequestContent(Request request) { using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) diff --git a/src/Ombi.Api/IApi.cs b/src/Ombi.Api/IApi.cs index 2b7f71bb8..e573d2d07 100644 --- a/src/Ombi.Api/IApi.cs +++ b/src/Ombi.Api/IApi.cs @@ -7,5 +7,6 @@ namespace Ombi.Api Task Request(Request request); Task Request(Request request); Task RequestContent(Request request); + T DeserializeXml(string receivedString); } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/RequestType.cs b/src/Ombi.Store/Entities/RequestType.cs index 42356985f..06cd6c069 100644 --- a/src/Ombi.Store/Entities/RequestType.cs +++ b/src/Ombi.Store/Entities/RequestType.cs @@ -6,7 +6,7 @@ namespace Ombi.Store.Entities { public enum RequestType { - TvShow, - Movie + TvShow = 0, + Movie = 1 } } diff --git a/src/Ombi/ClientApp/app/interfaces/IPlex.ts b/src/Ombi/ClientApp/app/interfaces/IPlex.ts index ccc4e0300..6e9a6b35a 100644 --- a/src/Ombi/ClientApp/app/interfaces/IPlex.ts +++ b/src/Ombi/ClientApp/app/interfaces/IPlex.ts @@ -49,6 +49,28 @@ export interface IPlexServerViewModel { servers: IPlexServerResult; } +export interface IPlexServerAddViewModel { + success: boolean; + servers: IPlexServersAdd[]; +} + +export interface IPlexServersAdd { + serverId: number; + machineId: string; + serverName: string; +} + +export interface IPlexUserViewModel { + username: string; + machineIdentifier: string; + libsSelected: number[]; +} + +export interface IPlexUserAddResponse { + success: boolean; + error: string; +} + export interface IPlexServerResult { friendlyName: string; machineIdentifier: string; diff --git a/src/Ombi/ClientApp/app/services/applications/plex.service.ts b/src/Ombi/ClientApp/app/services/applications/plex.service.ts index 5248e89f3..4a616ba33 100644 --- a/src/Ombi/ClientApp/app/services/applications/plex.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plex.service.ts @@ -6,7 +6,7 @@ import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; -import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel, IPlexServer, IPlexServerViewModel, IUsersModel } from "../../interfaces"; +import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel, IPlexServer, IPlexServerAddViewModel, IPlexServerViewModel, IPlexUserAddResponse, IPlexUserViewModel, IUsersModel } from "../../interfaces"; @Injectable() export class PlexService extends ServiceHelpers { @@ -22,10 +22,22 @@ export class PlexService extends ServiceHelpers { return this.http.post(`${this.url}servers`, JSON.stringify({ login, password }), {headers: this.headers}); } + public getServersFromSettings(): Observable { + return this.http.get(`${this.url}servers`, {headers: this.headers}); + } + public getLibraries(plexSettings: IPlexServer): Observable { return this.http.post(`${this.url}Libraries`, JSON.stringify(plexSettings), {headers: this.headers}); } + public getLibrariesFromSettings(machineId: string): Observable { + return this.http.get(`${this.url}Libraries/${machineId}`, {headers: this.headers}); + } + + public addUserToServer(user: IPlexUserViewModel): Observable { + return this.http.post(`${this.url}user`,JSON.stringify(user), {headers: this.headers}); + } + public getFriends(): Observable { return this.http.get(`${this.url}Friends`, {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts new file mode 100644 index 000000000..da63b67dd --- /dev/null +++ b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts @@ -0,0 +1,25 @@ +import { Component, Input } from "@angular/core"; +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +@Component({ + selector: "ngbd-modal-content", +template: ` + + + +`, +}) +export class AddPlexUserComponent { + + @Input() public name: string; + + constructor(public activeModal: NgbActiveModal) { + console.log("called"); + } + +} diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index 37ea2eea5..c38e87a51 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -1,8 +1,11 @@ 

User Management

- - +
+ +
+
+ @@ -127,4 +130,5 @@ - \ No newline at end of file + + diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts index dc137909e..6f48d4080 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts @@ -1,10 +1,28 @@ import { Component, OnInit } from "@angular/core"; -import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces"; -import { IdentityService, NotificationService, SettingsService } from "../services"; +import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; +import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IPlexLibraries, IPlexServersAdd, IUser } from "../interfaces"; +import { IdentityService, NotificationService, PlexService, SettingsService } from "../services"; +import { AddPlexUserComponent } from "./addplexuser.component"; @Component({ templateUrl: "./usermanagement.component.html", + styles:[`.modal-backdrop.fade{opacity:0.5} + .fade { + opacity:1 !important; + } + .modal { + display: none; + overflow: hidden; + position: fixed; + top: 100px; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + -webkit-overflow-scrolling: touch; + outline: 0; + }`], }) export class UserManagementComponent implements OnInit { @@ -20,10 +38,20 @@ export class UserManagementComponent implements OnInit { public availableClaims: ICheckbox[]; public bulkMovieLimit?: number; public bulkEpisodeLimit?: number; + public plexEnabled: boolean; + public plexServers: IPlexServersAdd[]; + public plexLibs: IPlexLibraries; + + public plexUsername: string; + public libsSelected: number[]; + public machineId: string; constructor(private identityService: IdentityService, private settingsService: SettingsService, - private notificationService: NotificationService) { } + private notificationService: NotificationService, + private plexSettings: SettingsService, + private plexService: PlexService, + private modalService: NgbModal) { } public ngOnInit() { this.users = []; @@ -31,11 +59,18 @@ export class UserManagementComponent implements OnInit { this.users = x; }); + this.plexSettings.getPlex().subscribe(x => this.plexEnabled = x.enable); + this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.settingsService.getEmailNotificationSettings().subscribe(x => this.emailSettings = x); } + public open() { + const modalRef = this.modalService.open(AddPlexUserComponent, {container:"ombi"}); + modalRef.componentInstance.name = "World"; + } + public welcomeEmail(user: IUser) { if (!user.emailAddress) { this.notificationService.error("The user needs an email address."); @@ -118,4 +153,34 @@ export class UserManagementComponent implements OnInit { this.order = value; } + + public getServers() { + if(!this.plexEnabled) { + return this.notificationService.error("Plex is not enabled"); + } + + this.plexService.getServersFromSettings().subscribe(x => { + if(x.success) { + this.plexServers = x.servers; + } + }); + } + + public getPlexLibs(machineId: string) { + this.plexService.getLibrariesFromSettings(machineId).subscribe(x => { + if(x.successful) { + this.plexLibs = x.data; + } + }); + } + + public addUser() { + this.plexService.addUserToServer({ username: this.plexUsername, machineIdentifier: this.machineId, libsSelected: this.libsSelected}).subscribe(x => { + if(x.success) { + this.notificationService.success("User added to Plex"); + } else { + this.notificationService.error(x.error); + } + }); + } } diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts index 40dba285d..cf25446f5 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts @@ -12,11 +12,12 @@ import { UserManagementEditComponent } from "./usermanagement-edit.component"; import { UserManagementComponent } from "./usermanagement.component"; import { PipeModule } from "../pipes/pipe.module"; -import { IdentityService } from "../services"; +import { IdentityService, PlexService } from "../services"; import { AuthGuard } from "../auth/auth.guard"; import { OrderModule } from "ngx-order-pipe"; +import { AddPlexUserComponent } from "./addplexuser.component"; const routes: Routes = [ { path: "", component: UserManagementComponent, canActivate: [AuthGuard] }, @@ -44,6 +45,10 @@ const routes: Routes = [ UserManagementAddComponent, UserManagementEditComponent, UpdateDetailsComponent, + AddPlexUserComponent, + ], + entryComponents:[ + AddPlexUserComponent, ], exports: [ RouterModule, @@ -51,6 +56,7 @@ const routes: Routes = [ providers: [ IdentityService, ConfirmationService, + PlexService, ], }) diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index 6ea37e9cc..8a627b710 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -127,6 +127,100 @@ namespace Ombi.Controllers.External } } + + [HttpGet("Libraries/{machineId}")] + [PowerUser] + public async Task GetPlexLibraries(string machineId) + { + try + { + var s = await PlexSettings.GetSettingsAsync(); + var settings = s.Servers.FirstOrDefault(x => x.MachineIdentifier == machineId); + var libs = await PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri); + + return new PlexLibrariesResponse + { + Successful = true, + Data = libs + }; + } + catch (Exception e) + { + _log.LogWarning(e, "Error thrown when attempting to obtain the plex libs"); + + var message = e.InnerException != null ? $"{e.Message} - {e.InnerException.Message}" : e.Message; + return new PlexLibrariesResponse + { + Successful = false, + Message = message + }; + } + } + + [HttpPost("user")] + [PowerUser] + public async Task AddUser([FromBody] PlexUserViewModel user) + { + var s = await PlexSettings.GetSettingsAsync(); + var server = s.Servers.FirstOrDefault(x => x.MachineIdentifier == user.MachineIdentifier); + var result = await PlexApi.AddUser(user.Username, user.MachineIdentifier, server.PlexAuthToken, + user.LibsSelected); + if (result.HasError) + { + return Json(new + { + Success = false, + Error = result.Error.Status + }); + } + else + { + return Json(new + { + Success = true + }); + } + } + + /// + /// Gets the plex servers. + /// + /// The u. + /// + [HttpGet("servers")] + [PowerUser] + public async Task GetServers() + { + try + { + var s = await PlexSettings.GetSettingsAsync(); + var servers = new List(); + foreach (var plexServer in s.Servers) + { + servers.Add(new PlexServersAddUserModel + { + ServerId = plexServer.Id, + MachineId = plexServer.MachineIdentifier, + ServerName = plexServer.Name + }); + } + + return Json(new + { + Success = true, + Servers = servers + }); + } + catch (Exception e) + { + _log.LogWarning(e, "Error thrown when attempting to obtain the GetServers for Add User VM"); + return Json(new PlexServersViewModel + { + Success = false, + }); + } + } + /// /// Gets the plex servers. /// diff --git a/src/Ombi/Models/External/PlexServersAddUserModel.cs b/src/Ombi/Models/External/PlexServersAddUserModel.cs new file mode 100644 index 000000000..8d272b37e --- /dev/null +++ b/src/Ombi/Models/External/PlexServersAddUserModel.cs @@ -0,0 +1,9 @@ +namespace Ombi.Models.External +{ + public class PlexServersAddUserModel + { + public string ServerName { get; set; } + public int ServerId { get; set; } + public string MachineId { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/Models/External/PlexUserViewModel.cs b/src/Ombi/Models/External/PlexUserViewModel.cs new file mode 100644 index 000000000..7dddf6a9f --- /dev/null +++ b/src/Ombi/Models/External/PlexUserViewModel.cs @@ -0,0 +1,9 @@ +namespace Ombi.Models.External +{ + public class PlexUserViewModel + { + public string Username { get; set; } + public string MachineIdentifier { get; set; } + public int[] LibsSelected { get; set; } + } +} \ No newline at end of file