Просмотр исходного кода

Merge pull request #1722 from TehMuffinMoo/v2-plugins

Add plexLibraries plugin
causefx 4 лет назад
Родитель
Сommit
4ecd41db91
7 измененных файлов с 536 добавлено и 1 удалено
  1. 8 0
      plexLibraries/README.md
  2. 54 0
      plexLibraries/api.php
  3. 10 0
      plexLibraries/config.php
  4. BIN
      plexLibraries/logo.png
  5. 195 0
      plexLibraries/main.js
  6. 248 0
      plexLibraries/plugin.php
  7. 21 1
      plugins.json

+ 8 - 0
plexLibraries/README.md

@@ -0,0 +1,8 @@
+# PlexLibraries Organizr Plugin
+## Summary
+
+This is an Organizr Plugin to allow both Administrators and users to manage plex libraries.
+
+### Features
+- Allows users to toggle plex libraries for their own user, configurable within the plugin settings.
+- Allows the Plex Administrator to toggle plex libraries for all users, without needing to use plex.tv.

+ 54 - 0
plexLibraries/api.php

@@ -0,0 +1,54 @@
+<?php
+$app->get('/plugins/plexlibraries/settings', function ($request, $response, $args) {
+	$plexLibrariesPlugin = new plexLibrariesPlugin();
+	if ($plexLibrariesPlugin->checkRoute($request)) {
+		if ($plexLibrariesPlugin->qualifyRequest(1, true)) {
+			$GLOBALS['api']['response']['data'] = $plexLibrariesPlugin->_pluginGetSettings();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->get('/plugins/plexlibraries/launch', function ($request, $response, $args) {
+	$plexLibrariesPlugin = new plexLibrariesPlugin();
+	if ($plexLibrariesPlugin->checkRoute($request)) {
+		if ($plexLibrariesPlugin->qualifyRequest($plexLibrariesPlugin->config['PLEXLIBRARIES-pluginAuth'], true)) {
+			$plexLibrariesPlugin->_pluginLaunch();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->get('/plugins/plexlibraries/shares', function ($request, $response, $args) {
+	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
+	if ($Organizr->checkRoute($request)) {
+		$plexLibrariesPlugin = new plexLibrariesPlugin;
+		if ($plexLibrariesPlugin->qualifyRequest($plexLibrariesPlugin->config['PLEXLIBRARIES-pluginAuth'], true)) {
+			$GLOBALS['api']['response']['data'] = $plexLibrariesPlugin->plexLibrariesPluginGetPlexShares();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->post('/plugins/plexlibraries/shares/{userId}/{action}/{shareId}', function ($request, $response, $args) {
+	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
+	if ($Organizr->checkRoute($request)) {
+		$plexLibrariesPlugin = new plexLibrariesPlugin;
+		if ($plexLibrariesPlugin->qualifyRequest($plexLibrariesPlugin->config['PLEXLIBRARIES-pluginAuth'], true)) {
+			$userId = $args['userId'] ?? null;
+			$action = $args['action'] ?? null;
+			$shareId = $args['shareId'] ?? null;
+			$plexLibrariesPlugin->plexLibrariesPluginUpdatePlexShares($userId, $action, $shareId);
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});

+ 10 - 0
plexLibraries/config.php

@@ -0,0 +1,10 @@
+<?php
+/*
+ * Always include PLUGINNAME-enabled
+ * Along with all your other settings from plugin.php
+ */
+return array(
+	'PLEXLIBRARIES-enabled' => false,
+	'PLEXLIBRARIES-librariesToInclude' => '',
+	'PLEXLIBRARIES-pluginAuth' => '1'
+);

BIN
plexLibraries/logo.png


+ 195 - 0
plexLibraries/main.js

@@ -0,0 +1,195 @@
+/* This file is loaded when Organizr is loaded */
+// Load once Organizr loads
+$('body').arrive('#activeInfo', {onceOnly: true}, function() {
+	plexLibrariesPluginLaunch();
+});
+// FUNCTIONS
+function plexLibrariesPluginLaunch(){
+	organizrAPI2('GET','api/v2/plugins/plexlibraries/launch').success(function(data) {
+		try {
+			var menuList = `<li><a href="javascript:void(0)" onclick="togglePlexLibrariesPlugin();"><i class="fa fa-tv fa-fw"></i> <span lang="en">Plex Libraries</span></a></li>`;
+			$('.append-menu').after(menuList);
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr);
+	});
+
+}
+function togglePlexLibrariesPlugin(){
+	let div = `
+		<div class="panel bg-org panel-info" id="plexLibraries-area">
+			<div class="panel-heading">
+				<span lang="en">Customise Plex Libraries</span>
+			</div>
+			<div class="panel-body">
+				
+				<div id="plexLibrariesTable">
+					<div class="white-box m-b-0">
+						<h2 class="text-center loadingPlexLibraries" lang="en"><i class="fa fa-spin fa-spinner"></i></h2>
+						<div class="row">
+							<div class="col-lg-12">
+								<select class="form-control" name="plexUsers" id="plexUsers" style="display:none">
+									<option value="">Choose a User</option>
+								</select><br>
+							</div>
+						</div>
+						<div class="table-responsive plexLibrariesTableList hidden" id="plexLibrariesTableList">
+							<table class="table color-bordered-table purple-bordered-table text-left">
+								<thead>
+									<tr>
+										<th width="20">Type</th>
+										<th>Name</th>
+										<th width="20">Action</th>
+									</tr>
+								</thead>
+								<tbody id="plexLibraries"></tbody>
+							</table>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		`;
+	swal({
+		content: createElementFromHTML(div),
+		button: false,
+		className: 'orgAlertTransparent',
+	});
+	plexLibrariesPluginLoadShares();
+}
+function plexLibrariesPluginLoadShares(){
+	organizrAPI2('GET','api/v2/plugins/plexlibraries/shares').success(function(data) {
+		$('.loadingPlexLibraries').remove();
+		try {
+			if (data.response.data.plexAdmin == false) {
+				$.each(data.response.data.libraryData, function(_, sharedServer) {
+					$.each(sharedServer.libraries, function(_, obj) {
+						var userId = sharedServer.id;
+						plexLibrariesPluginLoadSharesItem(obj,"",userId);
+						if($('.plexLibrariesTableList').hasClass('hidden')){
+							$('.plexLibrariesTableList').removeClass('hidden');
+						}
+					});
+				});
+			} else {
+				// Plex Admin response contains all users shares, mark all toggles as disabled whilst this is a work in progress.
+				$.each(data.response.data.libraryData, function(_, sharedServer) {
+					const thtml = $("#plexUsers ");
+					var dropdown = document.getElementById('plexUsers');
+					dropdown.style.display = "block";
+					var username = sharedServer.username;
+					var userId = sharedServer.id;
+					thtml.append('<option value="'+username+'">'+username+'</option>');
+					$.each(sharedServer.libraries, function(_, obj) {
+						plexLibrariesPluginLoadSharesItem(obj,username,userId);
+					});
+				});
+			}
+			const thtml = $("#plexLibraries ");
+			thtml.append('<script>plexLibrariesPluginOnToggle();</script>');
+			thtml.append('<script>plexLibrariesPluginOnSelect();</script>');
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+	}).fail(function(xhr) {
+		$('.loadingPlexLibraries').remove();
+		OrganizrApiError(xhr);
+	});
+}
+
+function plexLibrariesPluginLoadSharesItem(obj,username,userId){
+	const thtml = $("#plexLibraries ");
+	var mediaType = obj.type
+	var mediaShared = obj.shared
+	var mediaIcon = "0"
+	var checked = "";
+	switch(mediaType) {
+		case 'movie':
+			var mediaIcon = "video-clapper"
+			var mediaIconColour = "purple"
+			break;
+		case 'show':
+			var mediaIcon = "video-camera"
+			var mediaIconColour = "warning"
+			break;
+		case 'artist':
+			var mediaIcon = "music-alt"
+			var mediaIconColour = "info"
+			break;
+		case 'photo':
+			var mediaIcon = "camera"
+			var mediaIconColour = "danger"
+			break;
+	}
+	if (mediaShared == 1) {
+		var checked = "checked";
+	}
+	if (username === "") {
+		var username = "none"
+	}
+	let libItem = `
+		<tr class="plexUser ${username}">
+			<td><p class="text-${mediaIconColour}"><i class="ti-${mediaIcon} fa-2x" style="vertical-align: text-top;"></i></p></td>
+			<td>${obj.title}</td>
+			<td><input type="checkbox" class="js-switch plexLibraries" data-size="small" data-color="#99d683" data-secondary-color="#f96262" data-user-id="${userId}" value="${obj.id}" ${checked}></td>
+		</tr>
+	`;
+		thtml.append(libItem);
+}
+
+function plexLibrariesPluginOnToggle() {
+    $('.plexLibraries').change(function () {
+    	let userId = $(this).attr('data-user-id');
+        if (this.checked) {
+	        plexLibrariesPluginUpdateShare(userId,"share", this.value);
+        } else {
+	        plexLibrariesPluginUpdateShare(userId,"unshare", this.value);
+        }
+    });
+}
+
+function plexLibrariesPluginOnSelect() {
+    $('#plexUsers').change(function () {
+		Array.from(document.getElementsByClassName('plexUser')).forEach(
+			function(element, index, array) {
+				element.style.display = "none";
+			}
+		);
+		Array.from(document.getElementsByClassName(this.value)).forEach(
+			function(element, index, array) {
+				element.style.display = "table-row";
+			}
+		);
+		if($('.plexLibrariesTableList').hasClass('hidden')){
+			$('.plexLibrariesTableList').removeClass('hidden');
+		}
+    });
+}
+
+function plexLibrariesPluginUpdateShare(userId, action, shareId) {
+
+	$('#plexLibrariesTable').block({
+		message: '<h4><img src="plugins/images/busy.gif" width="50px" /> Just a moment...</h4>',
+		css: {
+			border: '1px solid #000',
+			color: '#fff',
+			background: '#1b1b1b'
+		}
+	});
+	organizrAPI2('POST','api/v2/plugins/plexlibraries/shares/' + userId + '/' + action + '/' + shareId, {}).success(function(data) {
+		try {
+			let response = data.response;
+			$('#plexLibrariesTable').unblock();
+			message('Plex Share',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+	}).fail(function(xhr) {
+		message('Plex Share',xhr.responseJSON.response.message,activeInfo.settings.notifications.position,"#FFF","error","5000");
+		$('#plexLibrariesTable').unblock();
+		OrganizrApiError(xhr);
+	});
+}
+// EVENTS and LISTENERS

+ 248 - 0
plexLibraries/plugin.php

@@ -0,0 +1,248 @@
+<?php
+// PLUGIN INFORMATION
+$GLOBALS['plugins'][]['plexlibraries'] = array( // Plugin Name
+	'name' => 'Plex Libraries', // Plugin Name
+	'author' => 'TehMuffinMoo', // Who wrote the plugin
+	'category' => 'Library Management', // One to Two Word Description
+	'link' => '', // Link to plugin info
+	'license' => 'personal', // License Type use , for multiple
+	'idPrefix' => 'PLEXLIBRARIES', // html element id prefix (All Uppercase)
+	'configPrefix' => 'PLEXLIBRARIES', // config file prefix for array items without the hypen (All Uppercase)
+	'version' => '1.0.0', // SemVer of plugin
+	'image' => 'api/plugins/plexLibraries/logo.png', // 1:1 non transparent image for plugin
+	'settings' => true, // does plugin need a settings modal?
+	'bind' => true, // use default bind to make settings page - true or false
+	'api' => 'api/v2/plugins/plexlibraries/settings', // api route for settings page (All Lowercase)
+	'homepage' => false // Is plugin for use on homepage? true or false
+);
+
+class plexLibrariesPlugin extends Organizr
+{
+	public function _pluginGetSettings()
+	{
+		$libraryList = [['name' => 'Refresh page to update List', 'value' => '', 'disabled' => true]];
+		if ($this->config['plexID'] !== '' && $this->config['plexToken'] !== '') {
+			$libraryList = [];
+			$loop = $this->plexLibraryList('key')['libraries'];
+			foreach ($loop as $key => $value) {
+				$libraryList[] = ['name' => $key, 'value' => $value];
+			}
+		}
+		$this->setGroupOptionsVariable();
+		return array(
+			'Settings' => array(
+				$this->settingsOption('token', 'plexToken'),
+				$this->settingsOption('button', '', ['label' => 'Get Plex Token', 'icon' => 'fa fa-ticket', 'text' => 'Retrieve', 'attr' => 'onclick="PlexOAuth(oAuthSuccess,oAuthError, null, \'#PLEXLIBRARIES-settings-page [name=plexToken]\')"']),
+				$this->settingsOption('password-alt', 'plexID', ['label' => 'Plex Machine']),
+				$this->settingsOption('button', '', ['label' => 'Get Plex Machine', 'icon' => 'fa fa-id-badge', 'text' => 'Retrieve', 'attr' => 'onclick="showPlexMachineForm(\'#PLEXLIBRARIES-settings-page [name=plexID]\')"']),
+				$this->settingsOption('auth', 'PLEXLIBRARIES-pluginAuth'),
+				$this->settingsOption('input', 'plexAdmin', ['label' => 'Plex Admin Username or Email']),
+				$this->settingsOption('plex-library-include', 'PLEXLIBRARIES-librariesToInclude', ['options' => $libraryList])
+			)
+		);
+	}
+	
+	public function _pluginLaunch()
+	{
+		$user = $this->getUserById($this->user['userID']);
+		if ($user) {
+			if ($user['plex_token'] !== null) {
+				$this->setResponse(200, 'User approved for plugin');
+				return true;
+			}
+		}
+		$this->setResponse(401, 'User not approved for plugin');
+		return false;
+	}
+	
+	public function plexLibrariesPluginGetPlexShares($includeAll = false, $userId = "")
+	{
+		if (empty($this->config['plexToken'])) {
+			$this->setResponse(409, 'plexToken is not setup');
+			return false;
+		}
+		$headers = array(
+			'Content-type: application/xml',
+			'X-Plex-Token' => $this->config['plexToken'],
+		);
+		// Check if user is Plex Admin
+		if ((strtolower($this->user['username']) == strtolower($this->config['plexAdmin']) || strtolower($this->user['email']) == strtolower($this->config['plexAdmin'])) && !$userId) {
+			$url = 'https://plex.tv/api/servers/' . $this->config['plexID'] . '/shared_servers/';
+			try {
+				$response = Requests::get($url, $headers, []);
+				if ($response->success) {
+					libxml_use_internal_errors(true);
+					$plex = simplexml_load_string($response->body);
+					$libraryList = array();
+					foreach ($plex->SharedServer as $child) {
+						if (!empty($child['username'])) {
+							$libraryList[(string)$child['username']]['username'] = (string)$child['username'];
+							$libraryList[(string)$child['username']]['email'] = (string)$child['email'];
+							$libraryList[(string)$child['username']]['id'] = (string)$child['id'];
+							$libraryList[(string)$child['username']]['userID'] = (string)$child['userID'];
+							foreach ($child->Section as $library) {
+								$library = current($library->attributes());
+								$libraryList[(string)$child['username']]['libraries'][] = $library;
+							}
+						}
+					}
+					$libraryList = array_change_key_case($libraryList, CASE_LOWER);
+					ksort($libraryList);
+					$apiData = [
+						'plexAdmin' => true,
+						'libraryData' => $libraryList
+					];
+					$this->setResponse(200, null, $apiData);
+					return $apiData;
+				} else {
+					$this->setResponse(500, 'Plex error');
+					return false;
+				}
+			} catch (Requests_Exception $e) {
+				$this->writeLog('error', 'PlexLibraries Plugin - Error: ' . $e->getMessage(), 'SYSTEM');
+				$this->setAPIResponse('error', 'PlexLibraries Plugin - Error: ' . $e->getMessage(), 400);
+				return false;
+			}
+		} else {
+			$searchTerm = ($userId) ?: $this->user['email'];
+			$searchKey = ($userId) ? 'shareId' : 'email';
+			$plexUsers = $this->allPlexUsers(false, true);
+			$key = array_search($searchTerm, array_column($plexUsers, $searchKey));
+			if ($key !== false) {
+				$url = 'https://plex.tv/api/servers/' . $this->config['plexID'] . '/shared_servers/' . $plexUsers[$key]['shareId'];
+			} else {
+				$this->setResponse(404, 'User Id was not found in Plex Users');
+				return false;
+			}
+			try {
+				$response = Requests::get($url, $headers, array());
+				if ($response->success) {
+					libxml_use_internal_errors(true);
+					$plex = simplexml_load_string($response->body);
+					$libraryList = array();
+					foreach ($plex->SharedServer as $child) {
+						if (!empty($child['username'])) {
+							$libraryList[(string)$child['username']]['username'] = (string)$child['username'];
+							$libraryList[(string)$child['username']]['email'] = (string)$child['email'];
+							$libraryList[(string)$child['username']]['id'] = (string)$child['id'];
+							$libraryList[(string)$child['username']]['shareId'] = (string)$plexUsers[$key]['shareId'];
+							foreach ($child->Section as $library) {
+								$library = current($library->attributes());
+								if (!$includeAll) {
+									$librariesToInclude = explode(',', $this->config['PLEXLIBRARIES-librariesToInclude']);
+									if (in_array($library['key'], $librariesToInclude)) {
+										$libraryList[(string)$child['username']]['libraries'][] = $library;
+									}
+								} else {
+									$libraryList[(string)$child['username']]['libraries'][] = $library;
+								}
+							}
+						}
+					}
+					$libraryList = array_change_key_case($libraryList, CASE_LOWER);
+					$apiData = [
+						'plexAdmin' => false,
+						'libraryData' => $libraryList
+					];
+					$this->setResponse(200, null, $apiData);
+					return $apiData;
+				} else {
+					$this->setResponse(500, 'Plex Error', $response->body);
+					return false;
+				}
+			} catch (Requests_Exception $e) {
+				$this->writeLog('error', 'PlexLibraries Plugin - Error: ' . $e->getMessage(), 'SYSTEM');
+				$this->setAPIResponse('error', 'PlexLibraries Plugin - Error: ' . $e->getMessage(), 400);
+				return false;
+			}
+		}
+	}
+	
+	public function plexLibrariesPluginUpdatePlexShares($userId, $action, $shareId)
+	{
+		if (!$userId) {
+			$this->setResponse(409, 'User Id not supplied');
+			return false;
+		}
+		if (!$action) {
+			$this->setResponse(409, 'Action not supplied');
+			return false;
+		}
+		if (!$shareId) {
+			$this->setResponse(409, 'Share Id not supplied');
+			return false;
+		}
+		if (!$this->qualifyRequest(1)) {
+			$plexUsers = $this->allPlexUsers(false, true);
+			$key = array_search($this->user['email'], array_column($plexUsers, 'email'));
+			if (!$key) {
+				$this->setResponse(404, 'User Id was not found in Plex Users');
+				return false;
+			} else {
+				if ($plexUsers[$key]['shareId'] !== $userId) {
+					$this->setResponse(401, 'You are not allowed to edit someone else\'s plex share');
+					return false;
+				}
+			}
+		}
+		$Shares = $this->plexLibrariesPluginGetPlexShares(true, $userId);
+		$NewShares = array();
+		if ($Shares) {
+			if (isset($Shares['libraryData'])) {
+				foreach ($Shares['libraryData'] as $key => $Share) {
+					foreach ($Share['libraries'] as $library) {
+						if ($library['shared'] == 1) {
+							$ShareString = (string)$library['id'];
+							if ($action == 'share') {
+								$NewShares[] = $ShareString;
+								$Msg = 'Enabled share';
+							} else {
+								$Msg = 'Disabled share';
+								if ($ShareString !== $shareId) {
+									$NewShares[] = $ShareString;
+								}
+							}
+						}
+					}
+				}
+				if ($action == 'share') {
+					if (!in_array($shareId, $NewShares)) {
+						$NewShares[] = $shareId;
+					}
+				}
+			}
+		}
+		if (empty($NewShares)) {
+			$this->setResponse(409, 'You must have at least one share.');
+			return false;
+		} else {
+			$http_body = [
+				"server_id" => $this->config['plexID'],
+				"shared_server" => [
+					"library_section_ids" => $NewShares
+				]
+			];
+			if ($userId) {
+				$url = 'https://plex.tv/api/servers/' . $this->config['plexID'] . '/shared_servers/' . $userId . '?X-Plex-Token=' . $this->config['plexToken'];
+			}
+			$headers = [
+				'Accept' => 'application/json',
+				'Content-Type' => 'application/json',
+			];
+			try {
+				$response = Requests::put($url, $headers, json_encode($http_body), []);
+				if ($response->success) {
+					$this->setAPIResponse('success', $Msg, 200, $http_body);
+					return $http_body;
+				} else {
+					$this->setAPIResponse('error', 'PlexLibraries Plugin - Error: Plex Error', 400);
+					return false;
+				}
+			} catch (Requests_Exception $e) {
+				$this->writeLog('error', 'PlexLibraries Plugin - Error: ' . $e->getMessage(), 'SYSTEM');
+				$this->setAPIResponse('error', 'PlexLibraries Plugin - Error: ' . $e->getMessage(), 400);
+				return false;
+			}
+		}
+	}
+}

+ 21 - 1
plugins.json

@@ -15,5 +15,25 @@
       "Main": "https://assets.hongkiat.com/uploads/automated-php-test/tdd-example.jpg",
       "Alternative": "https://i.stack.imgur.com/mCYvN.png"
     }
+  },
+  "Plex Libraries": {
+    "author": "TehMuffinMoo",
+    "category": "Library Management",
+    "description": "This is plugin to allow both Administrators and users to manage plex libraries from within Organizr.",
+    "icon": "https://raw.githubusercontent.com/causefx/Organizr/v2-plugins/plexLibraries/logo.png",
+    "contact": "https://discordapp.com/users/293873371379400705",
+    "website": "https://github.com/TehMuffinMoo/PlexLibraries",
+    "version": "1.0.0",
+    "release_date": "2021-10-03",
+    "last_updated": "2021-10-03",
+    "github_folder": "plexLibraries",
+    "license": "personal,business",
+    "images": {
+      "Plex Admin": "https://i.imgur.com/ebAwRp1.png",
+      "Plex Admin Dropdown": "https://i.imgur.com/tWwkGcZ.png",
+      "Plex User": "https://i.imgur.com/GFqr31t.png",
+      "Settings": "https://i.imgur.com/8E84afa.png",
+      "Menu Item": "https://i.imgur.com/zRrAEhO.png"
+    }
   }
-}
+}